← All news
AI SecurityAI AgentsVulnerability Management

Seven Products Independently Decided a Portability Layer Was a Security Boundary — and Every One of Them Was Wrong

Obiguard Research Team·August 25, 2026·9 min read

Pyodide is CPython compiled to WebAssembly. It was built so Python could run in a browser tab. Nowhere in its design documents does it claim to be a security boundary, and its maintainers have never marketed it as one.

That has not stopped an entire product category from using it as exactly that.

At DEF CON 34 — August 6–9 at the Las Vegas Convention Center — Saar Pearl and Vladimir Tokarev of Cyera presented WASM Was Not the Boundary: Sandcastles, Not Sandboxes, covering seven Pyodide sandbox escapes across workflow automation, spreadsheets, AI agent runtimes, desktop wrappers and CI tooling. Coverage of the talk has picked up over the past week, including eSecurity Planet's writeup. Four escapes carry public CVEs, scored 8.3 to 9.9.

Seven independent engineering teams, at seven organisations, with no coordination between them, made the same mistake in the same order. That is not seven bugs. That is a category error with a very high hit rate.

The mistake has a shape, and it repeats exactly

Each team reasoned the same way. We need to run untrusted Python — either code a user typed, or code a language model wrote. Full CPython in a container is heavy. Pyodide runs Python inside WebAssembly, and WebAssembly is sandboxed. Block os, block subprocess, block the js module. Ship it.

The first half is true. WebAssembly does have a memory-safety boundary. The second half is where it falls apart, because the products did not block the WebAssembly boundary from being crossed — they blocked some Python names. And a blocklist over a language with reflection is not a boundary; it is a list of the paths somebody happened to think of.

The Cyera research documents the three that were missed, over and over:

  • ctypes stayed reachable. ctypes.CDLL(None).system() calls into libc directly. No blocked module is involved, because ctypes is the escape hatch — that is its entire purpose.
  • Python's class hierarchy stayed traversable. object.__subclasses__() walks to warnings.catch_warnings and from there back to a full __builtins__. Every deleted name is recoverable if the object graph is intact.
  • Emscripten's exports stayed exposed. emscripten_run_script_string() executes JavaScript in the host runtime. Pyodide sits on Emscripten, and Emscripten's job is to bridge WASM to the host — so the bridge is there by construction.

None of these are exotic. They are the standard escape primitives from twenty years of Python sandbox failures, ported to a new substrate. What changed is not the technique. It is the number of systems that now hand a stranger's Python straight to an interpreter, because a model wrote it.

In Cohere's Terrarium, the escape is five tokens long

CVE-2026-5752, CVSS 9.3. Terrarium is Cohere's Docker-packaged sandbox for running code written by users or generated by an LLM — the reference implementation, in other words, of "let the model write Python and execute it safely."

The bug is a JavaScript prototype chain traversal, and it lives in how jsglobals is configured in service.ts. A mock document object was built as a plain object literal, which means it inherits from Object.prototype, which means the sandboxed code can walk from it to the Function constructor and compile code in the host realm:

fake_document.constructor.constructor("return globalThis")()

That expression hands back the outer Node.js globalThis, and with it require(). From a sandbox for untrusted code to arbitrary execution as root on the host process, via an object nobody thought of as an object.

The disclosure timeline is its own finding. Jeremy Brown — who found it using AI-assisted vulnerability research, which is the small joke inside this story — reported it to CERT/CC on February 19, 2026. CERT published VU#414811 on April 21, 61 days past the standard 45-day window with no vendor patch. Cohere shipped v1.0.1 the following day, then archived the repositoryas The Hacker News reported, no further patches are planned. If you vendored Terrarium into an internal tool, that patch was the last one you will ever get.

In n8n, the escape lands on the credential store

CVE-2025-68668, CVSS 9.9 — reported to n8n by Cyera's Vladimir Tokarev and Ofek Itach on October 27, 2025, patched December 24. Two bypasses of the blocklist: ctypes to reach libc.system(), and _pyodide._base.eval_code() where the security patches were not applied.

The CVSS is a 9.9 rather than a 9.1 because of one metric: scope changed. An authenticated user with permission to edit a workflow — an ordinary thing to grant — executes code as the n8n service process. That process holds the OAuth tokens, API keys and database passwords for every integration the instance connects to. Cyera's write-up calls it the keys to the kingdom, and the escalation path is worse than read-only: an attacker can write to the n8n database directly and promote themselves to admin.

This is the part that generalises. A sandbox escape is only as bad as the process that was holding the sandbox — and in the AI-automation category, that process is almost always the one holding every credential the automation needs. The isolation failure is a footnote. The credential concentration is the incident.

Grist shows the same shape on the data side: CVE-2026-24002, CVSS 9.1, nicknamed Cellbreak, where a single formula in a shared spreadsheet becomes host execution inside a platform that connects to databases, APIs and credentials. Hugging Face's smolagents — a framework whose entire premise is agents that write and run code — carries CVE-2026-10613 at 8.3. The research also names langchain-sandbox, stlite and cibuildwheel, which puts the same primitive inside your build pipeline.

The maintainers do not agree on whose problem this is

The most useful detail in the research is not technical. Across the seven products, responses split three ways: some teams re-architected, some archived the component, and some took the position that isolation is a deployment concern rather than a library one.

That third position is not unreasonable. Pyodide never promised confinement. But it leaves the operator holding a boundary that two of their vendors each believe the other is providing — and the CVSS scores land on the operator either way.

Grist's fix is instructive precisely because it is not a blocklist patch. They moved Pyodide under Deno, where escape primitives are mediated by capability-based permissions rather than by a list of forbidden names. Deployments that set GRIST_PYODIDE_SKIP_DENO=1 retain full risk. n8n's answer was similar in spirit: the vulnerable Pyodide path is now legacy and deprecated, with a runner-based architecture providing real process isolation.

Both vendors, independently, concluded the same thing — you do not fix a blocklist, you replace it with a permission model.

Assume the escape. Scope what it reaches

The practical planning assumption for anything that executes model-written code is that the sandbox will eventually be crossed. That is not defeatism; it is what seven-for-seven means. Which makes the operative question not is the sandbox airtight but what does the process holding it have access to — and that question is answerable today, without waiting for a patch.

Work through it this week:

  • Inventory where model-written code executes. Agent frameworks, workflow automation, notebook and formula surfaces, CI steps that run generated scripts. Anything embedding Pyodide, langchain-sandbox or stlite belongs on the list.
  • Look at the credentials on that process, not the sandbox config. If the executing process can read the integration credential store, the sandbox is the only thing standing between an authenticated workflow editor and every connected SaaS tenant.
  • Check for archived dependencies. Terrarium is the live example: patched once, then frozen. An unmaintained sandbox in a code-execution path is a decision you have already made by not making it.
  • Prefer a permission model over a deny list. Deno permissions, gVisor, or a separate process with no credentials at all. If the answer to "what stops this" is a list of blocked module names, you have the design that failed seven times.
  • Treat generated code as untrusted input, because it is. The model does not have to be malicious for this to matter. It only has to be influenced by something upstream — and the code it writes runs regardless.

Where Obiguard fits: Governance AI governs what the agent can reach, not just what it can run

The escapes in this research are all containment failures, and containment is not what Obiguard sells. What we do sell is the answer to the question containment failure exposes: when the boundary goes, what was on the other side of it.

Obiguard Governance AI treats that as a configuration rather than an assumption. Every AI agent lives in an inventory with a named owner and an assigned policy set, so "which agents execute code, and for which business function" is a lookup rather than an archaeology project. Allow-lists operate at four levels, binding each credential to specific model IDs, tools, external domains and invoking identities — which is the same lesson Grist and n8n each arrived at independently, applied one layer up. An escaped process that reaches for an unlisted domain is a denial, not a detection problem.

For actions outside an agent's intended scope, the Review Queue puts a human decision in front of execution, with the full trail from the original event to the resolution. And the audit ledger keeps the immutable per-call record of prompt, response, tool calls, model and initiating identity — so when a vendor discloses that the sandbox you were relying on was never a sandbox, "what did our agents do through it, and when" is a query you run rather than a reconstruction you attempt. That is the same argument we made about agents acting outside their authorisation: the useful control is not the one that predicts the failure, it is the one that bounds it.

Where the concern is people rather than pipelines — engineers pasting internal code into a consumer assistant that will happily execute it somewhere you cannot see — Obichat is the sanctioned on-ramp, running in your own tenant with per-workspace model allow-lists and an exportable activity log.

Seven teams looked at a portability layer and saw a security boundary. The reason to care is not that they were careless — the reason to care is that the assumption was reasonable right up until somebody tested it, and the thing on the other side was the credential store. Explore Governance AI or talk to us about what your code-executing agents are currently allowed to reach.

How Obiguard helps

Turn this into enforced policy, not just awareness.

Obiguard sits in front of every AI request your organization makes — screening prompts and outputs against the guardrails, compliance frameworks, and audit trails that stories like this one make necessary.

See how it works →