Security News

Inside Prefab UI’s Sandbox Escape: The Chain Behind CVE-2026-120338

A Prefab UI sandbox escape let Python code reach the host as the server's own OS user. See the chain behind CVE-2026-120338 and why 0.21.0 fixes it.

Brandefense logo 7 Min Read
Dark glass sandbox cube with a fractured lid leaking red light, cover art for Prefab UI Generative UI sandbox escape CVE-2026-120338

Prefab UI’s Generative UI sandbox runs caller-supplied Python inside Pyodide, Python compiled to WebAssembly, inside a Deno subprocess. The sandbox is documented as isolation for untrusted code, including UI text written by an AI model. CVE-2026-120338 shows that isolation failed completely, letting a caller who can submit code read and write files on the host as the server’s own operating system user.

The same write primitive could be turned into a standing backdoor, not just a one-time read. The flaw scores CVSS 3.1 10.0, Critical, and the maintainer closed it in prefab-ui 0.21.0, released 26 October 2026. Operators upgrade now.

A Sandbox Built for AI-Written Interfaces

Prefab is a Python framework for building interactive interfaces. It ships as a native part of FastMCP, the Python toolkit many teams use to serve Model Context Protocol tools and apps. Its Generative UI feature lets an AI agent write the Python that defines a UI component, then run that Python to render it.

Because that code comes from a model, not a trusted developer, Prefab UI runs it inside Pyodide, a WebAssembly build of Python, started inside its own Deno subprocess. The design looks sound on paper.

WebAssembly has no file or network access of its own. Deno is a runtime built to run untrusted JavaScript under an explicit permission system. The two layers should each hold the line on their own.

The Launch Flags That Never Got Restricted

The first break sits in how Prefab UI starts that Deno subprocess. The method PyodideSandbox._start lives in src/prefab_ui/sandbox/_pyodide.py. It launches Deno with --allow-read, --allow-write and --allow-net left unrestricted, open to any file or host the Deno process can reach. Deno’s own permission system, the one safeguard a WebAssembly sandbox depends on here, was switched off from the start.

The second break sits in the runner script that wires Pyodide to that process, src/prefab_ui/sandbox/runner.js. It leaves the Deno API sitting on the JavaScript global object, reachable from any code running in that process. Pyodide’s own js module exists to let Python code call JavaScript, so it proxies that same global object. The Python running inside the WebAssembly sandbox can call js.Deno.readTextFileSync and js.Deno.writeTextFileSync directly, with the same unrestricted permissions Deno started with.

None of this needed a bug in Pyodide, in Deno or in WebAssembly itself. Each layer worked exactly as designed. The flaw is a configuration gap between them: an unrestricted process wrapped in a bridge that was never told to hide it.

How the Sandbox Escape Reaches the Host
How the Sandbox Escape Reaches the HostAttack path in 6 steps: 1, generate_prefab_ui call (Caller), then code argument; 2, PyodideSandbox starts Deno (Sandbox), then unrestricted flags; 3, Runner leaves Deno on the JS global (Sandbox), then js global object; 4, Pyodide js module calls Deno (Deno), then readTextFileSync; 5, Host file read or written (Host), then as the OS user; 6, pth file dropped in site-packages (Host).CallerSandboxDenoHostcode argumentunrestricted flagsjs global objectreadTextFileSyncas the OS usergenerate_pr…callPyodideSand…starts DenoRunnerleaves Deno…Pyodide jsmodule call…Host fileread or…pth filedropped in…
  1. generate_prefab_ui callCallercode argument
  2. PyodideSandbox starts DenoSandboxunrestricted flags
  3. Runner leaves Deno on the JS globalSandboxjs global object
  4. Pyodide js module calls DenoDenoreadTextFileSync
  5. Host file read or writtenHostas the OS user
  6. pth file dropped in site-packagesHost

From a File Write to a Standing Backdoor

A file write on its own is a serious problem, but it ends when the process restarts. On prefab-ui 0.20.2, the same write was used to plant a .pth file inside the server’s own Python virtual environment, in its site-packages directory. Python reads every .pth file in that directory on startup and runs any import line it contains.

That means the next Python process that loads the same virtual environment, not only the one running the sandbox, runs the planted import automatically. A single call to generate_prefab_ui turns a one-time file write into code that runs again on every later start of that environment. That is a durable foothold, the kind that normally takes a working privilege escalation chain to get.

Who Could Reach the Sandbox

The common path in is FastMCP’s GenerativeUI provider. It registers an MCP tool named generate_prefab_ui and forwards that tool’s code argument straight into the Pyodide sandbox, with no extra check on what the code does. On a FastMCP server with no authentication configured, any client that can reach the server can call that tool. With authentication turned on, any client allowed to call tools at all can.

Generative UI is an opt-in feature, and the host running it needs Deno installed for the sandbox to start at all. A server that never registers the GenerativeUI provider, or never installs Deno, does not carry this path. That narrows the exposed population to teams who deliberately turned on AI-generated interfaces. That is exactly the part of the market this threat matters most to: companies building or buying agent tooling that renders model output as live, running code.

What a Defender Can Check For

The chain above points straight at what to look for. Check the installed prefab-ui version first; 0.14.0 through 0.20.2 carry the unrestricted launch flags. Check whether any FastMCP server in the environment registers GenerativeUI, and if it does, whether that server requires authentication before a client can call tools.

On a host that already ran an affected version, look at the Python virtual environment behind that FastMCP server. An unfamiliar .pth file in its site-packages directory, especially one added while the server was reachable by an untrusted client, is the sign this specific technique left behind. Process logs showing a Deno child process started with --allow-read, --allow-write and --allow-net together confirm the unrestricted launch the advisory describes.

The Fix in 0.21.0, and What to Do Now

Prefab UI’s maintainer merged the fix as pull request 538, commit 030f074, and shipped it as v0.21.0 on 26 October 2026. After the Deno runtime and Prefab’s own sources finish loading, the runner now revokes Deno’s read, write and network permission before the sandbox accepts any caller-supplied code.

The process also starts with --no-prompt, so a later permission request from inside the sandbox cannot grant those permissions back. The fix also limits the sandbox’s bootstrap network access to cdn.jsdelivr.net alone.

This is not the first time a sandbox meant to hold untrusted code has failed at its own boundary. A VMware ESXi sandbox escape reached a hypervisor host the same way, through a wall nobody had locked down fully.

A separate code injection flaw in openapi-typescript-codegen shows the same pattern: code that was never meant to run free, finding a path to do exactly that. Any team running code it did not write, model-generated or otherwise, is building the same kind of wall and needs to check it the same way.

What to Do Now
  1. Upgrade prefab-ui to 0.21.0 or later on every host that runs the Generative UI sandbox.Closes CVE-2026-120338 by revoking Deno’s file and network permissions before code runs.
  2. Require authentication on any FastMCP server that registers the GenerativeUI provider.An unauthenticated server lets any client that can reach it call generate_prefab_ui.
  3. Check site-packages in the affected virtual environment for an unfamiliar .pth file.A planted .pth file is the sign this specific persistence technique was used.
  4. Turn off the GenerativeUI provider on any server that does not need to render AI-generated code.Removes the remote path into the sandbox entirely until the upgrade is confirmed.

Prefab UI’s own permission model, Deno’s, and Pyodide’s were each sound by themselves. What failed was the agreement between them: the unrestricted launch flags one layer assumed the next would restrict.

Any team that lets a model write code, then runs that code inside a remote code execution sandbox built for that exact purpose, should read this chain as a checklist. It fits one team’s own setup, not just this one framework’s patch note.

Frequently Asked Questions.

What does CVE-2026-120338 let an attacker do?

A caller who can submit code to Prefab UI's Generative UI sandbox can read and write files on the host as the server's own operating system user, and can persist that access by planting a .pth file in the server's Python virtual environment.

Which prefab-ui versions are affected, and what fixes it?

prefab-ui 0.14.0 through 0.20.2 start the sandbox's Deno subprocess with unrestricted read, write and network permission. prefab-ui 0.21.0, released 26 October 2026, revokes those permissions before the sandbox accepts any code.

Does an attacker need to be authenticated to exploit it?

No, if the FastMCP server that registers the GenerativeUI provider has no authentication configured. With authentication on, any client allowed to call tools can still reach the sandbox; JFrog Security Research scores that authenticated case 9.9 against the unauthenticated case's 10.0.

Who found and reported the Prefab UI sandbox escape?

Yuval Moravchick of the JFrog Security Research Team found and reported CVE-2026-120338, credited through JFrog's own CNA on the CVE record.

Is CVE-2026-120338 in CISA's Known Exploited Vulnerabilities catalog?

Not as of 1 November 2026.

Sources

  1. 1 NVD: CVE-2026-120338
  2. 2 JFrog Security Research: JFSA-2026-001699278
  3. 3 GitHub: PrefectHQ/prefab
  4. 4 GitHub: PrefectHQ/prefab pull request #538 (fix)
  5. 5 CISA Known Exploited Vulnerabilities Catalog
Share