Your Prefab UI Upgrade Won't Remove Files Attackers Already Wrote

A FastMCP server running GenerativeUI on prefab-ui 0.14.0 through 0.20.2 lets a client read and write host files. Version 0.21.0 fixes it, but earlier writes stay, so inspect .pth files before you upgrade.

By Rajesh Beri·November 1, 2026·9 min read
Share:
A server rack in a dim data center with a small glass terrarium on the shelf in front of it, the terrarium's lid cracked open and a thin cable running from inside it into the server's open drive bay. No text or logos in

Illustration generated using AI

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:

  1. 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" .
  1. 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 .pth files 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
  1. Upgrade prefab-ui by name, and update the lockfile in the same change: python -m pip install --upgrade "prefab-ui>=0.21.0".
  2. If you can't ship that today, follow JFrog's interim advice and stop registering GenerativeUI on servers untrusted clients can reach.

This Week:

  1. 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.
  2. If you log tool arguments, have your security lead search generate_prefab_ui calls for code that references Deno or imports js. A hit is an incident, while a clean search of incomplete logs proves little.
  3. Add prefab-ui>=0.21.0 as an explicit constraint in every service that uses fastmcp[apps], so a resolver can't settle on an older version.

This Month:

  1. 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.
  2. 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.
  3. 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

Share:

Frequently Asked Questions

What is CVE-2026-120338?

It is a flaw in prefab-ui 0.14.0 through 0.20.2. Generative UI code runs in Pyodide inside a Deno subprocess with broad permissions, and the Deno API is reachable from Python, so a caller can read and write host files as the server's OS user. JFrog scores it CVSS 10.0 without authentication and 9.9 with it.

Is my FastMCP server affected by the prefab-ui vulnerability?

Only if it sends untrusted code to the Prefab sandbox on an affected prefab-ui version, usually by registering the opt-in GenerativeUI provider from the fastmcp[apps] extra. Custom tools that call Sandbox directly count too. HTTP servers carry the most risk because FastMCP defaults to auth=None, while the default STDIO transport can only be reached by a local client.

Does upgrading to prefab-ui 0.21.0 remove an attacker's access?

No. Version 0.21.0 revokes Deno's read, write and network permissions before generated code runs, but it does not remove files already written. Check the virtualenv for planted .pth files, rebuild exposed servers from a clean image and rotate secrets the server's OS user could read.

Why should I check .pth files before running pip?

Python executes import lines in .pth files at every interpreter startup unless -S is passed, and pip runs on Python. Running pip inside a compromised virtualenv would trigger a planted file. Inspect the directory with find and grep first, then compare against a clean build from the same lockfile.

Will upgrading FastMCP fix CVE-2026-120338?

Not reliably. The apps extra on FastMCP's main branch requires prefab-ui>=0.18.0, which vulnerable versions satisfy, and pip's default only-if-needed strategy leaves a satisfying dependency alone. Upgrade prefab-ui by name, update your lockfile and add a prefab-ui>=0.21.0 constraint.

Newsletter

More on AI Security, every Sunday

The week's enterprise AI news, AI Security included, in one Sunday email.

Weekly, free. Unsubscribe any time. Privacy

Latest Articles

View All →