If a FastMCP server of yours registers GenerativeUI on prefab-ui 0.14.0 through 0.20.2, any client that can call its generate_prefab_ui tool can read and write files on that host as the server's OS user. The fix is prefab-ui 0.21.0, but the upgrade leaves in place anything an attacker already wrote, and one of those writes can run code the next time Python starts in that environment.
JFrog published CVE-2026-120338 on November 1, scoring it CVSS 10.0 on a server with no authentication and 9.9 on one with authentication. Strix's tracker lists it as not in CISA's KEV catalog, with NVD analysis still pending, and none of the sources cited here reports exploitation in the wild as of November 2, 2026.
How a Sandbox Tool Reached the Host Filesystem
The sandbox had a bridge out of it, and the process on the far side of that bridge held full permissions. Prefab runs caller-supplied Python in Pyodide (CPython compiled to WebAssembly) inside a Deno subprocess. JFrog's advisory says the Deno API stayed reachable from sandboxed Python through the js bridge, so WebAssembly isolation did not stop a call into Deno's file functions.
The launcher supplied the rest. Prefab's 0.20.2 code starts Deno with --allow-read, --allow-write and --allow-net, none of them scoped to a path or host. Deno's documentation warns that a bare --allow-net "grants the whole category", and it also says all code on one thread shares one privilege level.
The 0.20.2 security documentation described something else: "The Deno process has read-only filesystem access (to load Prefab source)", followed by "It has no write, environment variable, or FFI access." The launcher in the same tag passes --allow-write. If your security review of this package relied on its documentation, the review passed on a claim the code contradicted.
JFrog's proof of concept, run against 0.20.2 with no authentication, read /etc/passwd and wrote a new file to the host. The same write can plant a .pth file in the server's virtualenv. A .pth file is a path configuration file that Python's site module reads at startup, and lines starting with import are executed every time the interpreter starts unless -S is passed.
Which Servers Are Exposed
You are exposed only if a server sends untrusted code to this sandbox, and that narrows the blast radius a long way. JFrog states that servers that never execute untrusted code in this sandbox "are not exposed", and the feature is opt-in: you add it with mcp.add_provider(GenerativeUI()). The provider is not the only way in, because Prefab also documents calling Sandbox directly, including from your own MCP tool. FastMCP's own claim that it powers 70% of MCP servers tells you nothing about this bug, and none of these sources counts the servers that turned the provider on.
The strongest case for staying calm is that FastMCP's default transport is STDIO, which only a local client can reach. A desktop tool you launched yourself, feeding it code you trust, sits at the low end of this risk. Treat that case as a routine upgrade.
One default removes the comfort once the server speaks HTTP. FastMCP servers "default to auth=None", which is the CVSS 10.0 scenario. Don't count on a missing Deno binary either. The 0.20.2 launcher raises an error when Deno isn't on PATH, but FastMCP's docs say Deno "installs automatically on first use", so check the host.
| Your deployment | Who can reach the tool | What to do |
|---|---|---|
| STDIO, local client only | Whoever or whatever drives that client | Upgrade; check .pth files if the client handles untrusted content |
HTTP, auth=None, reachable beyond localhost |
Anyone with network access | Treat as compromised until shown otherwise |
| HTTP behind authentication | Any holder of a valid credential | Treat as compromised if credentials go beyond people you trust |
| Authenticated, model-driven client | Whoever can place text in front of the model | Assess for prompt injection, then decide |
The last row comes from one independent write-up, which argues that prompt injection in documents or web pages could steer model-written code into the sandbox even for authenticated clients. Neither that write-up nor JFrog's advisory demonstrates the chain against this CVE, but the tool exists to run model-written Python, so you should plan for it.
Why Your Normal Patch Process May Have Missed It
The fix shipped six days before the CVE was published. PR #538 merged on October 25 under the title "Refresh dependencies and fix renderer and sandbox regressions." The v0.21.0 release notes lead with a feature and mention "sandbox lifecycle and output handling fixes" under runtime and packaging, with no change labelled as a security fix. PyPI dates 0.21.0 and 0.21.1 to October 26. The CVE followed on November 1.
Upgrading FastMCP alone may not help. FastMCP's apps extra requires prefab-ui>=0.18.0 on its main branch, a floor that still admits vulnerable releases. pip's default only-if-needed strategy leaves a dependency alone when its installed version already satisfies the requirement, so pip install --upgrade fastmcp can leave 0.20.2 in place. FastMCP's release list, through v4.1.5 on November 1, shows no security entry for GenerativeUI either.
You also may not see prefab-ui in your own manifest, because fastmcp[apps] pulls it in. Explicit pins hold a vulnerable version in place too: one community generative UI server listed on Glama states it deliberately pins fastmcp[apps]==4.0.5 and prefab-ui==0.20.2, and it offers HTTP transports as well as STDIO.
The advisory IDs don't line up yet. JFrog and Strix point to GHSA-595q-jv4r-q7j9, which returned a 404 when checked on November 2. GitHub's advisory database carries the CVE as GHSA-3g72-rrqc-f76c, with the package and version fields blank. If your scanner matches on structured advisory data, confirm that it flags 0.20.2 before you trust a clean report, and go by the affected range Strix lists: at least 0.14.0 and below 0.21.0.
What the Upgrade Fixes and What It Leaves
Version 0.21.0 closes the file and network path, and it leaves earlier compromise untouched. The new launcher adds --no-prompt and narrows network access to cdn.jsdelivr.net:443. The new runner calls Deno.permissions.revoke for read, write and net before it accepts code. Both pieces are needed, since Deno says revoking only returns a permission "to the prompt state". The write-up reports that JFrog's payloads now fail with permission errors.
The v0.21.0 runner shows no step that removes the Deno global, so the protection is the revoked permission. The launcher still passes unscoped --allow-read and --allow-write for bootstrap. That is a reasonable design with one line of defense, and Prefab now says so itself. Its current documentation states: "WASM execution alone does not establish a host security boundary." It also says: "The interpreter and imported modules remain shared between calls, so this is not per-request Python isolation."
If several tenants share one server, read that second sentence as a limit on what you can promise them, even on the patched version.
The exposure ran longer than the headline suggests. PyPI dates 0.14.0 to March 27, 2026 and the fix to October 26, so the affected range was the current release for about seven months. A .pth file is only the documented route to persistence: a process that can write as the server's user can also change anything else that user can write. And with --allow-net unscoped before the fix, a read could leave the host immediately, with no restart needed. If a server held provider keys in a readable file, the steps in the LiteLLM compromise response and the Bedrock key rotation guide apply here.
What to Do About It
Order matters on this one, because executable .pth lines run at every Python startup, and that includes the pip you are about to run. Look before you upgrade.
Today:
- Find every registration and every resolved version, without starting Python in the suspect environment:
grep -rnE "GenerativeUI|prefab_ui\.sandbox" --include="*.py" .
grep -rn -A1 "prefab-ui" --include="*.lock" --include="requirements*.txt" .
- On any server an untrusted client could reach, inspect the virtualenv with tools that don't start Python, and compare the results against a clean build from the same lockfile. Some
.pthfiles are legitimate, including those from editable installs, so the diff is what you want to read:
find /path/to/venv -name '*.pth' -ls
grep -n '^import' /path/to/venv/lib/python*/site-packages/*.pth
- Upgrade prefab-ui by name, and update the lockfile in the same change:
python -m pip install --upgrade "prefab-ui>=0.21.0". - If you can't ship that today, follow JFrog's interim advice and stop registering
GenerativeUIon servers untrusted clients can reach.
This Week:
- For each HTTP server that ran an affected version without authentication, rebuild it from a clean image and rotate every secret in a file its OS user could read. A fresh container clears planted files, and it does nothing about keys that have already been read.
- If you log tool arguments, have your security lead search
generate_prefab_uicalls for code that referencesDenoor importsjs. A hit is an incident, while a clean search of incomplete logs proves little. - Add
prefab-ui>=0.21.0as an explicit constraint in every service that usesfastmcp[apps], so a resolver can't settle on an older version.
This Month:
- Test every code sandbox you run with the three checks the write-up suggests: read a file outside its directory, write one, open an outbound connection. Add the results to the vendor file, since the 0.20.2 documentation would not have told you.
- Move model-written code to a boundary that holds on its own. E2B, Modal and Daytona are compared on isolation and price in our agent sandbox guide.
- Require authentication on every HTTP MCP server, and enforce which servers clients may use with the approach in our MCP governance guide.
The Bottom Line
The MCP ecosystem has now hit this Pyodide problem at least twice in 2026, with different consequences each time. Pydantic archived mcp-run-python on January 30, warning in its README that "Python code running in pyodide can run arbitrary javascript" and pointing users to a replacement called Monty. JFrog then published CVE-2026-25905 against it on February 9, at CVSS 5.8, calling the archived project unlikely to receive a fix.
Prefab's first affected release arrived 46 days later. The bridge behaved the same way, and unscoped Deno flags gave it access to the host, which helps explain the jump from 5.8 to 10.0. The OSV entry for the earlier flaw tells users to move to a Docker container or a WASM environment with the JS bridge disabled.
The same advice fits any MCP tool that takes code as an argument, whichever framework it ships in. For prefab-ui this week, run the find before the upgrade, because the upgrade starts Python in that environment.
Continue Reading
- LiteLLM's October 21 Compromise Puts Provider Keys at Risk
- vLLM 0.30.0 Can Run Code Through Ghostscript Images
- MCP Server Governance: Enforce at the Client, Not the Registry
- E2B vs Modal vs Daytona: Idle Time Sets Sandbox Bills
- Your Agent Allowlist Says Python. It Means Any Command.
- Rotate Your Bedrock Keys If You Used These Nine AI Platforms
