Mar 14, 2026·8 min read·2 visits
A sandbox escape in SimpleEval (< 1.0.5) allows attackers to achieve unauthenticated remote code execution. Attackers can bypass the function blocklist by chaining attribute accesses on permitted objects to reach forbidden modules like 'os' or 'sys'.
SimpleEval versions prior to 1.0.5 contain a critical sandbox escape vulnerability. The expression evaluator fails to validate objects retrieved via attribute access or returned as expression results, allowing attackers to traverse from safe objects to dangerous Python modules and achieve arbitrary code execution.
SimpleEval is a Python library designed to evaluate single, mathematically focused expressions safely within a restricted execution environment. The library serves as a sandbox by replacing the native Python eval() function, processing an Abstract Syntax Tree (AST) of the input expression, and explicitly restricting access to dangerous built-in functions. Developers typically use SimpleEval to allow end-users to compute dynamic values or filter data without exposing the host system to arbitrary code execution.
The vulnerability, identified as CVE-2026-32640, resides in the handling of object attribute access and the validation of evaluated results. While SimpleEval maintains a blocklist of forbidden functions via the DISALLOW_FUNCTIONS set, it historically only applied this check to direct function calls. It failed to restrict the types of objects that could be accessed through dot notation or returned as part of a Python container object. This oversight maps directly to CWE-915 (Improperly Controlled Modification of Dynamically-Determined Object Attributes) and CWE-94 (Improper Control of Generation of Code).
Because Python utilizes a highly introspective object model, seemingly benign modules or objects often contain references to powerful system modules. If an attacker identifies a permissible object passed into the SimpleEval names context dictionary, they can traverse its attributes to locate a reference to a restricted module. Once a restricted module is reached, the attacker can invoke its functions to escape the sandbox.
The impact of this vulnerability is high. An attacker who successfully escapes the SimpleEval sandbox gains arbitrary code execution privileges matching those of the underlying Python process. This compromises the integrity of the application and the host system, rendering the intended security boundary ineffective.
The root cause of CVE-2026-32640 is the absence of recursive type and identity validation during AST node evaluation. Specifically, the _eval_attribute and _eval methods in the SimpleEval class processed attribute lookups without validating the safety of the resulting Python object. In Python, modules encapsulate their namespaces within a __dict__, and importing a module within another module creates a permanent reference to it as an attribute.
When SimpleEval evaluated an ast.Attribute node, it resolved the attribute against the parent object and returned the raw Python object. For example, if the os.path module was explicitly provided in the names dictionary for harmless path string manipulations, the evaluator permitted access to it. However, the os.path module internally imports the os module, creating an attribute reference back to the parent os module. The evaluator allowed the expression path.os to resolve successfully, returning the unconstrained os module to the execution context.
Furthermore, the DISALLOW_FUNCTIONS blocklist was insufficient because it relied on hashing the exact function object at the top level of execution. It did not traverse container objects such as lists, tuples, or dictionaries. If an expression resolved to a container holding a disallowed function, the sandbox allowed the container to be constructed and returned or manipulated in subsequent expressions. This created an alternative leakage vector where dangerous references bypassed the initial security checks entirely.
The core architectural flaw was treating object references and execution contexts as static. By trusting the origin of an object (the names dictionary) rather than continuously validating the resultant object of every operation against a strict policy, the sandbox allowed attackers to extract protected references from permitted ones.
Prior to version 1.0.5, the _eval_attribute function simply fetched the attribute using Python's built-in getattr(). It lacked any post-retrieval validation to determine if the fetched attribute was a module or a disallowed function. The sandbox relied almost entirely on preventing the initial exposure of dangerous functions, operating under the incorrect assumption that safe objects only contained safe attributes.
# Vulnerable implementation logic (Pre-1.0.5)
def _eval_attribute(self, node):
# ... node evaluation ...
return getattr(owner, node.attr)The fix introduced in commit 1654cbf0219345f707c79664b8657be6b8d23e33 implements defense-in-depth object validation. The patch modifies _eval_attribute and introduces a new recursive validation method called _check_disallowed_items. This method is invoked on the output of every evaluated node, ensuring that no execution path can result in a leaked module or forbidden function.
# Patched validation logic (1.0.5)
def _check_disallowed_items(self, item):
if isinstance(item, ModuleWrapper):
return
if isinstance(item, types.ModuleType):
raise FeatureNotAvailable("Sorry, modules are not allowed")
if isinstance(item, Hashable) and item in DISALLOW_FUNCTIONS:
raise FeatureNotAvailable("This function is forbidden")
# The method then recurses into lists, tuples, and dictsAdditionally, the patch introduces the ModuleWrapper class. This security proxy allows developers to expose specific attributes of a module without exposing the underlying raw module object. The wrapper blocks access to private attributes (those starting with _) and enforces an explicit allowed_attrs set, moving the library from an insecure opt-out model to a strict opt-in model for attribute access.
Exploitation of CVE-2026-32640 requires an attacker to supply a crafted expression to a SimpleEval instance where the host application has injected a complex Python object into the names context dictionary. The attacker must possess knowledge of the injected object's class hierarchy or imported modules to construct a viable attribute traversal path.
The primary attack vector utilizes dot notation to traverse the object graph until an execution sink is reached. If the os.path module is provided as path, the attacker supplies the payload path.os.popen('id').read(). The AST parser evaluates path.os, resolving to the raw os module. It then evaluates the popen attribute and executes the command, returning the system output to the attacker.
A secondary vector involves container leakage. If the application provides a list containing a disallowed function (e.g., names={"funcs": [exec, eval]}), the attacker can bypass the DISALLOW_FUNCTIONS check by accessing the function via its list index. The payload funcs[0]('exit()') extracts the exec function from the list and immediately invokes it, executing arbitrary code.
Nested object attributes present a third vector. If a custom class instance is passed into the sandbox, and that class holds a class-level reference to a disallowed built-in (e.g., class Foo: p = exec), the attacker accesses it via thing.p('exit()'). The pre-1.0.5 evaluator fails to inspect the type of thing.p, permitting the invocation of the restricted execution function.
The exploitation of CVE-2026-32640 yields severe consequences, reflected in its CVSS 4.0 score of 8.7. The vulnerability requires no privileges (PR:N) and no user interaction (UI:N). The attack complexity is low (AC:L) because Python's standard library structure is predictable and well-documented, allowing attackers to construct reliable traversal paths across different environments.
Successful exploitation results in arbitrary code execution on the host machine. The executed payload runs within the context and privilege level of the underlying Python process. If the application running SimpleEval operates with elevated privileges or has access to sensitive files, the attacker inherits these capabilities. This leads to a complete compromise of application integrity (VI:H).
The vulnerability fundamentally breaks the security guarantees of the SimpleEval library. Applications utilizing SimpleEval to isolate untrusted user input, such as mathematical evaluation APIs, dynamic filtering engines, or configuration parsers, become explicit remote code execution vectors. The lack of network-level authentication requirements for the exploit means any exposed endpoint utilizing a vulnerable SimpleEval instance is at immediate risk.
While this CVE is not currently listed in the CISA Known Exploited Vulnerabilities (KEV) catalog, the widespread use of SimpleEval in data science and web applications makes it an attractive target. The availability of clear proof-of-concept exploits within the patch test suite accelerates the potential for weaponization by threat actors.
The definitive remediation for CVE-2026-32640 is upgrading the simpleeval package to version 1.0.5 or higher. The official patch fundamentally changes the object validation architecture, successfully closing the attribute traversal and container leakage vectors. Developers must update dependencies and redeploy affected applications immediately.
For applications where immediate patching is not feasible, developers must audit the objects provided to the SimpleEval names dictionary. Ensure that no modules, complex class instances, or containers holding functions are passed into the execution context. Limit the names dictionary strictly to primitive data types (strings, integers, floats) to eliminate traversal paths.
When upgrading to version 1.0.5, implement the newly introduced ModuleWrapper class if module access is explicitly required by the application. Instead of passing raw modules, wrap them and define a strict allowed_attrs set. This enforces a principle of least privilege on attribute access.
# Secure configuration example (Post-1.0.5)
from simpleeval import SimpleEval, ModuleWrapper
import math
# Wrap the module and restrict access to specific safe attributes
safe_math = ModuleWrapper(math, allowed_attrs={'sqrt', 'ceil'})
s = SimpleEval(names={'math': safe_math})CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
simpleeval danthedeckie | < 1.0.5 | 1.0.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-915, CWE-94 |
| Attack Vector | Network |
| CVSS 4.0 Score | 8.7 |
| Impact | Remote Code Execution / Sandbox Escape |
| Exploit Status | Proof of Concept Available |
| CISA KEV | Not Listed |
Improperly Controlled Modification of Dynamically-Determined Object Attributes