Python Sandbox Escape via Generator/Coroutine Frame Object Introspection
Some Python sandboxes block dangerous code by rejecting any attribute name starting with an underscore, like __class__ or __globals__. This rule catches a bypass: generator and coroutine objects expose frame-related attributes (gi_frame, cr_frame, ag_frame) that don't start with underscore, and chaining them with f_back, f_locals, f_builtins, or f_globals gets an attacker to the same forbidden territory anyway.
How the attack works
An attacker submits code to a sandboxed Python evaluator that only blocks underscore-prefixed attribute names. Instead of touching __globals__ directly, they access a generator or coroutine's gi_frame/cr_frame/ag_frame attribute, then chain .f_back, .f_locals, .f_builtins, or .f_globals to walk up to the caller's frame. From there they can pull __import__ out of f_builtins, __builtins__ out of f_globals, or directly find and mutate an import allowlist (calling .append()/.extend()/.remove() on it) sitting in some frame's local variables. This has been disclosed against crawl4ai's expression evaluator and a smolagents executor, in one case triggered through a malicious __str__ or __repr__ method invoked by a str() call.
Netzilo detection
Netzilo reports this behaviour when it is observed.
- Signature ID
- 110a53c6-9a79-4134-9d00-763828cd31d6
- Severity
- Critical
Why it matters
An attacker escapes the intended code sandbox, gaining access to unrestricted builtins or import capability, which can lead to arbitrary code execution or the ability to import and run system-level modules like os.
What you can do
- →Do not allowlist based on underscore-prefix filtering alone; also block gi_frame, cr_frame, ag_frame, f_back, f_locals, f_builtins, and f_globals regardless of naming convention.
- →Treat any import allowlist or authorization list as untrusted if it lives in a frame reachable from evaluated code; store such policy data outside the interpreter's reach.
- →Review any custom __str__/__repr__ methods on objects passed into sandboxed evaluation, since calling str() or repr() on them can trigger arbitrary code.
- →Manually review matches that look like documentation or CTF write-ups quoting this technique, rather than auto-dismissing them as false positives.
Known benign look-alikes
- Legitimate debugging/introspection code using sys._getframe() or inspect.currentframe() that does not go through a generator/coroutine's gi_frame/cr_frame/ag_frame attribute
- Documentation or CTF write-ups quoting this exact technique for educational purposes — should be reviewed by a human, not auto-allowlisted, since the same string appears in both attack and defense