- Book: System Design Pocket Guide: Fundamentals
- The series: Pocket Guides for Developers — 8 short books on agents, RAG, prompts, observability, events, databases and system design — the whole series
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
You add one line to your MCP server so the model can draw a chart for the user instead of describing it. The model writes a little Python, a sandbox runs it, and a UI comes back. The word "sandbox" is doing a lot of work in that sentence.
On November 1, JFrog Security Research published CVE-2026-120338 against Prefab UI, the generative UI framework from PrefectHQ. The GitHub advisory rates it CVSS 10.0. In versions 0.14.0 through 0.20.2, a caller who could submit code to the Generative UI sandbox could read and write files on the host.
The fix already exists. prefab-ui 0.21.0 shipped on October 26, six days before the CVE went public, and 0.21.1 followed the same day. If you run FastMCP with Generative UI turned on, check this one before anything else on your list.
What the sandbox actually allowed
Prefab's Generative UI runs caller-supplied Python inside Pyodide, Python compiled to WebAssembly, in a Deno subprocess. That's two layers of isolation on paper. Per JFrog's write-up, two choices cancelled both.
First, the launcher started Deno with unrestricted --allow-read, --allow-write and --allow-net. Deno's own security docs say "Deno is secure by default", and a bare flag with no value "grants the whole category." So the outer layer granted full filesystem and network access.
Second, the runner left the Deno API on the JavaScript global object. Pyodide's js module proxies that global, so Python code could call js.Deno.readTextFileSync(...) and js.Deno.writeTextFileSync(...) on the host (JFrog). The WebAssembly boundary didn't matter, because the code inside it had a phone line straight out.
JFrog also notes that the runner comment and the public docs described the process as read-only. It wasn't.

The sandbox had walls. The floor had a hatch.
From file write to code execution
Reading files is bad. Writing them is worse. On 0.20.2, JFrog used the write capability to plant a .pth file in the server virtualenv's site-packages, and the next Python process using that environment ran the planted import (JFrog).
That works because of a Python feature that predates all of this. The site module docs say that in .pth files, "lines starting with import (followed by space or tab) are executed with exec()." One written file turns into code that runs on every interpreter start in that environment, outside any sandbox.
How FastMCP exposes it
The usual remote path is FastMCP's GenerativeUI provider. The FastMCP docs show the setup as a single call, mcp.add_provider(GenerativeUI()), which registers generate_prefab_ui: "a tool that accepts Python code, executes it in a Pyodide sandbox, and renders the result as a Prefab app." JFrog says the provider forwards that code argument into the sandbox without additional checks.
Who can call that tool is your auth story. FastMCP's authentication docs say "FastMCP servers default to auth=None." The 10.0 score assumes that case: no login, any reachable client. With authentication, any client allowed to call tools still reaches the bug, which JFrog scores 9.9 (JFrog).
There's also a quieter path. The tool takes code the model wrote. If a prompt injection in a document or web page steers the model's output, an authenticated, well-meaning client can still hand the sandbox hostile code. The OWASP LLM01 entry covers how that steering happens.
Two conditions limit exposure. Generative UI is opt-in, and the host must have Deno (GitHub advisory). Don't lean on the second one too hard: the FastMCP docs say the server-side sandbox "requires Deno — it installs automatically on first use." Neither the GitHub advisory nor Strix's CVE entry reports exploitation in the wild, and Strix lists the CVE as not in CISA's KEV catalog.
What 0.21.0 changes
The fix landed in PR #538, merged on October 25 as commit 030f074. Its description says "bootstrap filesystem/network permissions are revoked before generated code executes." Per JFrog, that breaks into three changes:
- After the runtime and Prefab sources load, the runner revokes Deno read, write and network permissions before it accepts code.
- Deno starts with
--no-prompt, so a later permission request can't win them back. - Bootstrap network access is limited to
cdn.jsdelivr.net:443.
JFrog reports that on 0.21.0 and later, both its read and write payloads fail with a Deno permission error. This is the pattern Deno's docs recommend: "Dropping permissions you no longer need after startup is a simple way to shrink the attack surface."

0.21.0 in one picture: load what you need, switch the permissions off, then accept code.
Check your servers
Run these in each environment that serves FastMCP. They're standard pip, grep and shell commands, nothing Prefab-specific:
# which prefab-ui is installed here? python -m pip show prefab-ui # is the GenerativeUI provider registered anywhere? grep -rn "GenerativeUI" --include="*.py" . # could this host run the sandbox? command -v deno # upgrade; 0.21.1 was latest on PyPI as of Nov 1, 2026 python -m pip install --upgrade "prefab-ui>=0.21.0" fastmcp[apps] is the extra that brings in prefab-ui (FastMCP docs), so a server can have it without anyone adding it by name. Check your lockfile for the version it resolved, and look for a pin on 0.20.x.
If a vulnerable server was reachable by untrusted clients, upgrading doesn't undo a write that already happened. Activate that server's virtualenv first, so python is its interpreter and not whatever comes first on your PATH, then look at its .pth files:
# run with the server's virtualenv activated SP=$(python -c \ "import sysconfig; print(sysconfig.get_paths()['purelib'])") ls -la "$SP"/*.pth Some .pth files are normal, for example from editable installs. Open any you don't recognise and read its import lines. If you can't upgrade yet, JFrog's advice is plain: don't register GenerativeUI on a server untrusted clients can reach.
"Sandbox" is a claim. Test it
This bug wasn't exotic. Any code path that runs model-written code has the same shape: Python in Pyodide, JavaScript in a worker, shell in a container.
So treat the word "sandbox" in a README as a claim to verify, and test it the way JFrog did. Run code inside it that tries to read a file outside its directory and then write one. After that, try an outbound connection. If any of that works, you don't have a sandbox yet.
Do you run model-written code anywhere in your stack? What actually stops it from touching the disk? Tell me in the comments.
If this was useful
A sandbox is one more component in your system with a boundary, and where that boundary sits is a system-design decision. The System Design Pocket Guide: Fundamentals covers the core building blocks those systems are made of, so you can reason about where a new component like this one fits. It's one of 8 short books in the Pocket Guides for Developers series, covering agents, RAG, prompts, observability, events, databases and system design.
I also build Hermes IDE, free and source-available on macOS, Windows and Linux, which runs Claude Code, Codex and other agents side by side, each in its own terminal and worktree (GitHub).
