The Incident
On 2026-09-15, IBM published GHSA-xm98-3vcf-fph7 for CVE-2026-53710 (CVSS 10.0, CWE-94 Code Injection) in the python_sandbox_server subproject of its mcp-context-forge AI gateway. The component ships an execute_code MCP tool that is advertised as running user-supplied Python inside a RestrictedPython sandbox; in every build before python_sandbox_server 1.0.2 the sandbox’s own safe_builtins exposed raw getattr without the required _getattr_ guard, and the companion validate_code check scanned only for literal dunder substrings. An attacker constructs dunder attribute names at runtime, traverses the Python class hierarchy to reach subprocess.Popen, and executes arbitrary OS commands with the server process’s privileges. The HTTP and SSE transports exposing the tool required no authentication in the vulnerable range — the 1.0.2 fix released on 2026-05-25 introduced a mandatory SANDBOX_API_TOKEN of at least 32 characters on the HTTP transport, which itself documents what the prior default was.
The core Context Forge gateway and proxy components are not directly affected; the defect lives in the python_sandbox_server subproject that ships alongside them. Release 1.0.2 consolidated 59 pull requests and shipped the fix along with other hardening — it removed getattr and setattr from safe_builtins, added guarded wrappers, and replaced the dangerous-string substring scanner with an ast.walk() over attribute nodes. The CVE was published roughly four months after that fix shipped, so operators who stayed current with the May release carried the patch before public disclosure. No in-the-wild exploitation of CVE-2026-53710 has been publicly reported at the time of writing.
MITRE ATT&CK coverage: T1059 — Command and Scripting Interpreter, T1190 — Exploit Public-Facing Application.
The Authority Path That Failed
The identity carrying execution authority at the moment of failure is the python_sandbox_server process — whichever OS user the operator runs it as, with whatever filesystem, network, and credential reach that user holds in its deployment context. The scope the operator intended to grant was bounded Python execution: run user-supplied code against a RestrictedPython sandbox, return the result. The scope the server actually exercised was arbitrary OS command execution, because its own safe_builtins dictionary exposed a raw getattr primitive and its validator scanned only for literal strings an attacker could trivially concatenate at runtime. The gap between those two scopes is the entire difference between a sandboxed REPL and an unauthenticated remote shell.
Two trust anchors failed in sequence. The compile-time anchor was RestrictedPython’s enforcement that dangerous dunder access must be routed through _getattr_; that enforcement did not fire because the server’s own builtins dict shipped the unwrapped getattr. The runtime anchor was validate_code’s substring scan for literals like __class__ or __import__; that check did not fire because an attacker never has to write the forbidden string — they build it from parts at runtime. Neither failure requires an authenticated session: in the vulnerable range the execute_code tool was reachable over HTTP and SSE without any credential. The operator deployed a sandbox-named tool server; the network received an unauthenticated OS-exec service.
SecurityV0 Perspective
This fits unproven_execution. RestrictedPython has corner cases, and _getattr_ exists precisely because raw getattr is dangerous — those are facts about the library, not news. The durable defect is a framework shipping a tool whose named purpose (sandbox) and actual exposure (OS shell) do not match, on an unauthenticated transport by default. The operator who deployed mcp-context-forge for its advertised AI-gateway role received, as part of the same deployment, an execute_code MCP tool that any network-reachable party could invoke to run arbitrary subprocess calls as the server user. That gap — between what the operator authorized and what the framework exposed — is the pattern SV0 flags, and it extends from python_sandbox_server to every MCP tool server whose advertised purpose understates its real authority.
The evidence pack for this finding would enumerate, per mcp-context-forge deployment: the resolved version of the python_sandbox_server subproject (below 1.0.2 or not), the set of MCP tools the server registers (is execute_code among them), the transport configuration (HTTP, SSE, stdio) and the authentication posture of each (is SANDBOX_API_TOKEN set, and is it long enough), the network exposure of the management plane (publicly reachable, VPC-peer-reachable, or operator-only), and the identity under which the server runs (container user, mounted secrets, cloud-role binding). Before exfiltration the pack answers one question: does this deployment permit any caller — authenticated or not — to invoke execute_code. After the fact it answers the forensic question: during the exposure window, which execute_code invocations occurred, with what Python payload, and what child processes did the sandbox process spawn.
What To Do
- Upgrade
python_sandbox_serverto 1.0.2 across everymcp-context-forgedeployment. Pin the subproject version in container images, lockfiles, and Helm charts rather than relying on a floating tag. The core gateway version does not imply the subproject version — verify both. - Set
SANDBOX_API_TOKENon the HTTP transport and rotate it on a short schedule. The token the 1.0.2 release introduces is the only authentication layer the HTTP transport ships; without a rotation policy, a leaked token is a durable backdoor. Treat it as a secret with a TTL, stored in your secret manager with the same handling as a production database password. - Front HTTP and SSE transports with a fail-closed network boundary. Default-bind the sandbox process to
127.0.0.1, place a reverse proxy or mesh-mTLS layer in front, and refuse any request whose claimed identity cannot be mapped to a known operator. The sandbox’s exposure, not its code, is what turns the primitive into an incident. - Audit every MCP tool server for the sandbox-named, OS-exec-effective pattern. The specific defect was named “sandbox,” but any tool server that spawns subprocesses, writes files, or evaluates expressions on behalf of an agent is a candidate for the same analysis. Record which tools your gateway registers, what each tool can do, and whether the operator-authorized scope matches the exposed authority.
- Instrument subprocess spawns from the sandbox host and alert on non-allowlisted parent-child chains. If
python_sandbox_serveris the parent of a/bin/sh,/bin/bash, or/usr/bin/pythonprocess executing anything outside the RestrictedPython entry point, that is the exploitation signal. The identities enumerated in the evidence pack above are the correlation set; the alert closes the loop.