P.K. SHARMA

Cyber security intelligence, AI governance, practitioner analysis

Prefab UI's sandbox for model written code let FastMCP tool callers write host files, CVE-2026-120338 says

Read on 1 November 2026: for 213 days, Prefab UI's sandbox for model written code gave that code read, write and network access to the host. Any caller of FastMCP's generate_prefab_ui tool could write files and persist, says CVE-2026-120338. Fixed in 0.21.0; no exploitation stated.

By · LinkedIn · · 16 min read

213 days with the walls open

Prefab UI, PrefectHQ's Python framework for interfaces written by a language model, shipped a sandbox for that model written code in version 0.14.0 on 27 March 2026. It was fixed in 0.21.0 on 26 October 2026. That is 213 days (derived: counted from the GitHub release times, which match the PyPI times in the CVE record to within a minute), and 21 tagged releases sit inside the affected range (derived: counted from the project's release list). For the last 145 of those days, 0.20.2 was the newest release, so anyone installing the current version got an affected one (derived).

The record, CVE-2026-120338, published by JFrog as CNA at 15:24 UTC on 1 November, says that through that whole range the sandbox's Deno process was started with unrestricted read, write and network permission, and the Deno API was left where the sandboxed Python could reach it. "A caller who can supply the code can read and write files as the server OS user", in the record's words, and can make that stick by writing a .pth file into the server's virtualenv. The usual way in is FastMCP's GenerativeUI provider, whose tool generate_prefab_ui hands its code argument straight to the sandbox.

What those figures do not establish: that anyone used the flaw, or how many servers were ever exposed. The record states no exploitation, and CISA's Known Exploited Vulnerabilities catalogue (version 2026.10.30, read at 21:38 GMT) does not list it. The 10.0 score describes one scenario, a FastMCP server with Generative UI switched on, Deno installed and no authentication; nothing in the record counts such servers, and this briefing does not estimate one. Everything here was read from the primary pages between 21:34 and 21:50 GMT on Sunday 1 November 2026: the CVE record, GitHub's advisory, the fixing pull request and commit, the release notes, the tagged source and the Prefab and FastMCP documentation.

What the record says

The CVE record is titled "Prefab UI Pyodide sandbox escape to host RCE". It names three weaknesses: CWE-693 (protection mechanism failure), CWE-94 (code injection) and CWE-269 (improper privilege management). It points at two files, src/prefab_ui/sandbox/_pyodide.py, the Python launcher, and src/prefab_ui/sandbox/runner.js, the JavaScript runner. The affected range is prefab-ui 0.14.0 up to but not including 0.21.0, and the record says the flaw was "Confirmed on prefab-ui 0.20.2".

It carries two CVSS 3.1 scores, each tied to a written scenario. With no authentication the vector is AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H, base score 10.0. For an authenticated client allowed to call tools it is the same vector with PR:L, base score 9.9. Both are critical. GitHub's copy, GHSA-3g72-rrqc-f76c, shows only the 10.0.

The record credits a researcher as finder and JFrog Security Research as coordinator. Its timeline is unusually candid: the flaw was reported to PrefectHQ on 5 August 2026 as GHSA-595q-jv4r-q7j9, and the record says that advisory "is still in triage and has no CVE id". The repository advisory link answered with a 404 when this site read it, so it was not public then. The record also says of the 0.21.0 release: "No vendor advisory text was published with the release."

How the wall came down

Prefab's sandbox runs Python inside Pyodide, which is CPython compiled to WebAssembly, and runs Pyodide inside a Deno process. WebAssembly on its own cannot open a file on the host. Deno can, if it has been given permission, and the guard on the host is Deno's permission flags. In 0.20.2 the launcher started Deno with --allow-read, --allow-write and --allow-net, with no path or host limit on any of them.

The second half of the flaw is reach. The runner left the Deno API on JavaScript's global object, and Pyodide gives Python a js module that proxies that global. So, as the record puts it, the Python "can call Deno.readTextFileSync and Deno.writeTextFileSync". A wall made of WebAssembly with a door in it to a process that may read and write anything is not a wall.

The record names what a caller does with that: reads and writes files as the operating system user that runs the server, and persists "code execution by writing a .pth file into the server virtualenv's site-packages". Python's own site documentation explains why that works: in a .pth file, "lines starting with import (followed by space or tab) are executed with exec()" when the interpreter starts. So a file write becomes code that runs every time the server's Python starts. The record describes file access and persistence; it does not describe anything taken from a real server.

The path from an MCP tool call to host files. Drawn by this site from the CVE-2026-120338 record (JFrog, 1 November 2026) and fixing commit 030f074, read 1 November 2026. The prompt injection box is this site's reading and is not in the record. Mechanism only: no code is shown.

The fix changes three things, and the record lists the same three. After Pyodide and the Prefab sources load, the runner revokes Deno's read, write and network permissions before it accepts any code. Deno is started with --no-prompt, so a permission request cannot grant them back. And the network flag at start up is narrowed to cdn.jsdelivr.net:443, the content delivery network the runtime loads from. The launcher in 0.21.0 still starts with read and write allowed, because start up needs them, and drops them before the first request.

The friendly name: a sandbox, documented as one

The word doing the work here is "sandbox". It told developers they could hand model output to this code without thinking about the host. The project said so in plain terms. The pull request that added it in March opens: "Prefab can now safely execute LLM-generated Python code that builds component trees." The Generative UI page at 0.20.2 said: "The code runs in a Pyodide WASM sandbox so untrusted LLM output can't access the host system." Its security paragraph added: "It has no write, environment variable, or FFI access."

The launcher in the same release passed --allow-write. The runner's own header comment, from the first version, said the same thing as the page, "No write, env, or FFI access", a few lines away from a process that had write access. The documentation described the design someone intended; the flags described the one that shipped. Nothing checked one against the other for 213 days.

The page today is more careful, and the change is the best summary of the flaw anyone has written: "WASM execution alone does not establish a host security boundary." It also now says that the interpreter is shared between calls, "so this is not per-request Python isolation". Those two sentences are worth reading as a general rule about any runtime sold as a sandbox for model written code.

What the sandbox process could do before and after 0.21.0. Drawn by this site from the tagged 0.20.2 and 0.21.0 source, fixing commit 030f074 and the Prefab documentation, read 1 November 2026.

Who can reach it: the FastMCP route

FastMCP, also from PrefectHQ, is a Python framework for building Model Context Protocol servers. Its Generative UI page marks the feature "New in version 3.2.0", and the 3.2.0 release is dated 30 March 2026, three days after Prefab 0.14.0. One line, mcp.add_provider(GenerativeUI()), registers generate_prefab_ui, a second tool for searching components, and a renderer. The page says the tool "accepts Python code, executes it in a Pyodide sandbox", and that when the model finishes, "the server runs the complete code in a server-side Pyodide sandbox for validation". That server side run is the sandbox in the CVE.

Who counts as the caller depends on authentication, and the record spells out both cases. FastMCP's authentication page is explicit about the default: "FastMCP servers default to auth=None", and over HTTP that means the endpoint accepts requests without a token and unprotected components "are accessible to anyone who can reach it". With an auth provider configured, the record says any client allowed to call tools can still reach the sandbox; FastMCP's authorization page documents per tool checks such as require_scopes that narrow that set. The same page notes that over STDIO "all auth checks are skipped" and access is "controlled by the local execution environment".

Two preconditions narrow it. Generative UI is opt in: a server without the provider line does not expose the tool. And Deno must be present. Here the documents disagree. The record says "the host must have Deno installed", and Prefab's launcher, in 0.20.2 and today, raises an error if it cannot find deno on the path. FastMCP's Generative UI page says the server side sandbox "requires Deno" and that "it installs automatically on first use". This site did not test which is true on a fresh install, so treat Deno's absence as something to check, not something to rely on.

One more line matters for anyone who installed through FastMCP. Its package metadata on the main branch, read today, declares the apps extra as prefab-ui>=0.18.0. That floor does not exclude the affected versions, so an environment locked before 26 October can hold 0.18.0 to 0.20.2 and still satisfy FastMCP.

A fix filed under another name

The fix landed on 25 October in pull request 538, titled "Refresh dependencies and fix renderer and sandbox regressions", 81 days after the report (derived). The change that matters is one of sixteen bullet points in the squashed commit, "Revoke sandbox bootstrap permissions before execution", among dependency updates, chart fixes and documentation builds. The 0.21.0 release notes of 26 October describe it, if at all, as "sandbox lifecycle and output handling fixes". The CVE followed six days later, 88 days after the report (derived).

A fix that ships under the name of a regression or a dependency refresh is common in open source, and the site has written about it before: the MCP Python SDK fix that shipped 21 days before its advisory. The cost falls on the people who triage upgrades. Anyone reading the 0.21.0 notes to decide whether the upgrade was urgent had nothing to go on until the CVE appeared.

The downstream signal is weak too. GitHub's advisory for this CVE is marked "Unreviewed", lists "No package", gives affected and patched versions as "Unknown", and says Dependabot alerts are not supported on it. GitHub's documentation gives the general rule: "Dependabot doesn't create Dependabot alerts for unreviewed advisories as this type of advisory isn't checked for validity or completion." A team that waits for a Dependabot pull request will not get one from this advisory as it stood when read.

What the record states, and what it does not

CVE-2026-120338: stated on the record, and not stated. From the CVE record, GitHub's advisory, the fix and the FastMCP documentation, read 1 November 2026.

QuestionStatedNot stated
Affected versionsprefab-ui 0.14.0 up to but not including 0.21.0; confirmed on 0.20.2How many installs or downloads fall in the range
Who can trigger itAnyone who reaches a FastMCP server with no authentication; with authentication, any client allowed to call toolsHow many servers run Generative UI, or are reachable from the internet
What a caller getsFile read and write as the server OS user; persistence through a .pth fileAny data taken from any real server
ExploitationNone stated; not in KEV catalogue 2026.10.30 at 21:38 GMTWhether anyone used it before or after the fix
Prompt injection routeNot mentionedWhether a steered model could reach it (this site's reading only)
Vendor advisoryReported 5 August as GHSA-595q-jv4r-q7j9, still in triage per the recordAny vendor advisory text; the link returned 404 when read
Fix0.21.0, commit 030f074, pull request 538Any backport to an older release line
Deno preconditionHost must have Deno installed (record)FastMCP's docs say Deno installs on first use; not tested here

What a 10.0 encodes, and what it does not

The 10.0 vector says: reachable over the network, low complexity, no privileges, no user interaction, and a changed scope, meaning the damage lands outside the component that has the flaw, here the host outside the sandbox. High impact on confidentiality, integrity and availability follows from file read, file write and persistence. Those are properties of the flaw under the stated scenario.

They are not properties of any particular server. The vector does not say a server exists that matches the scenario, that one is reachable from the internet, or that anyone has tried. A server with an auth provider scores 9.9, not 10.0, because it needs a client with tool access; a server without the provider line is not affected at all. The score tells you how bad the flaw is when the preconditions hold. Whether they hold is a question about your own deployment, which is where the checklist starts.

What to do, in order

Take this with you

Ranked actions for teams running Prefab UI or FastMCP

  • Find every environment with prefab-ui installed, from lock files and from the environments themselves, and every FastMCP server that calls add_provider(GenerativeUI()) or exposes a tool named generate_prefab_ui. A renamed tool counts: GenerativeUI takes a tool_name option.
  • Upgrade prefab-ui to 0.21.0 or later and confirm the installed version inside each running environment, not in a requirements file. FastMCP's apps extra asks only for prefab-ui 0.18.0 or later, so an old lock can keep an affected version. (0.21.1 followed 18 minutes after 0.21.0 and keeps the fix; checked in its source.)
  • Do not wait for a Dependabot alert. GitHub's advisory lists no package and says Dependabot alerts are not supported on it. Match on the CVE record's range in whatever scanner you use.
  • Put an auth provider on every FastMCP server served over HTTP; FastMCP's documentation says the default is auth=None. Then restrict who can call generate_prefab_ui with a per tool check such as require_scopes, as FastMCP's authorization page documents.
  • Until you can upgrade, remove the GenerativeUI provider line; the feature is opt in. Prefab's launcher refuses to start without deno on the path, but FastMCP's docs say Deno installs on first use, so check the host rather than assume. (This step is this site's suggestion.)
  • Hunt on any server that ran an affected version with the tool exposed: look in the virtualenv's site-packages for .pth files you cannot account for, especially lines starting with import, and on Python 3.15 for .start files too. The record names .pth persistence; the hunt is this site's suggestion.
  • Treat files readable by the server's operating system user as possibly read on any server that was open: rotate secrets in that account's reach. (This site's suggestion; the record states read access, not any theft.)
  • Run model written code under an operating system boundary, a separate user or a container with no host mounts and no network, rather than a language runtime alone. This is this site's view; Prefab's own page now says WASM alone is not a host boundary.

Technical details

The artifacts the primary sources publish, copied exactly. There is no exploit or payload here, and none is published in the primaries; the record describes the reach in words only. Use these to identify affected installs, confirm the fix is in place and check server configuration.

The CVE description as the CNA published it. It is the most complete public statement of the flaw, its preconditions and the fix.

Text
Prefab UI's Generative UI sandbox runs caller-supplied Python inside Pyodide, Python compiled to WebAssembly, in a Deno subprocess. The feature is documented as isolation for untrusted code, including LLM-written UI. From prefab-ui 0.14.0 through 0.20.2 the launcher starts Deno with unrestricted --allow-read, --allow-write, and --allow-net, and the runner leaves the Deno API on the JavaScript global object. Pyodide's js module proxies that global, so the Python can call Deno.readTextFileSync and Deno.writeTextFileSync. A caller who can supply the code can read and write files as the server OS user, and can persist code execution by writing a .pth file into the server virtualenv's site-packages. The usual remote path is FastMCP's GenerativeUI provider, which registers the MCP tool generate_prefab_ui and forwards that tool's code argument into this sandbox. On a FastMCP server with no authentication configured, any client who can reach the server can call the tool. With authentication, any client allowed to call tools can. Generative UI is opt-in, and the host must have Deno installed. Confirmed on prefab-ui 0.20.2. Fixed in 0.21.0: after the runtime and Prefab sources load, the runner revokes Deno read, write, and network permission before it accepts code, the process is started with --no-prompt so a permission request cannot grant them again, and bootstrap network access is limited to cdn.jsdelivr.net. The fix is commit 030f074 (pull request 538).
CVE-2026-120338 description, from the CVE record published by JFrog as CNA on 1 November 2026, read 1 November 2026.

The two CVSS 3.1 scores, each with the scenario the CNA attached to it. The difference between them is only the privileges required.

Text
Scenario: FastMCP server with GenerativeUI enabled, Deno installed, and no authentication configured
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H  baseScore 10  CRITICAL

Scenario: Authenticated client allowed to call tools on a server that executes untrusted code in the Generative UI sandbox
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H  baseScore 9.9  CRITICAL
CVSS 3.1 metrics and scenarios, from the CVE-2026-120338 record, read 1 November 2026.

The affected range and the two files the record points at, as printed in the record's affected block. A scanner rule can match on the package name and the range.

Text
{
  "vendor": "PrefectHQ",
  "product": "prefab-ui",
  "packageName": "prefab-ui",
  "programFiles": [
    "src/prefab_ui/sandbox/_pyodide.py",
    "src/prefab_ui/sandbox/runner.js"
  ],
  "versions": [
    {
      "status": "affected",
      "version": "0.14.0",
      "lessThan": "0.21.0",
      "versionType": "semver"
    }
  ],
  "defaultStatus": "unaffected"
}
Affected block (selected fields), from the CVE-2026-120338 record, read 1 November 2026.

The launch flags before and after, as the fixing commit prints them in the Python launcher. Lines starting with a minus were removed, lines with a plus were added. A defender can check the installed _pyodide.py for --no-prompt and the narrowed --allow-net to confirm a host has the fix.

Text
@@ -72,10 +75,14 @@ async def _start(self) -> None:
                 [
                     deno,
                     "run",
+                    "--no-config",
+                    f"--lock={DENO_LOCK}",
+                    "--frozen",
                     "--unstable-detect-cjs",
+                    "--no-prompt",
                     "--allow-read",
                     "--allow-write",
-                    "--allow-net",
+                    "--allow-net=cdn.jsdelivr.net:443",
                     str(RUNNER_JS),
                     str(PREFAB_SRC),
                 ],
Launcher change in src/prefab_ui/sandbox/_pyodide.py, from PrefectHQ's fixing commit 030f074 on GitHub, read 1 November 2026.

The lines the fix added to the JavaScript runner, which drop the three permissions after start up and before the request loop. Their presence in the installed runner.js is the other check.

Text
+// Bootstrap is complete: ordinary Prefab code uses the mounted virtual
+// filesystem and preloaded packages. Do not retain bootstrap I/O permissions
+// while executing generated code.
+for (const name of ["read", "write", "net"]) {
+  await Deno.permissions.revoke({ name });
+}
+
Permission revocation added to src/prefab_ui/sandbox/runner.js, from PrefectHQ's fixing commit 030f074 on GitHub, read 1 November 2026.

How a FastMCP server switches the feature on, as FastMCP's documentation prints it. Searching a code base for GenerativeUI and add_provider finds the servers that expose the tool.

Text
from fastmcp import FastMCP
from fastmcp.apps.generative import GenerativeUI

mcp = FastMCP("Prefab Studio")
mcp.add_provider(GenerativeUI())
Enabling Generative UI, from FastMCP's Generative UI documentation page, read 1 November 2026.

FastMCP's statement of its authentication default, which decides whether the 10.0 or the 9.9 scenario applies to a given server.

Text
FastMCP servers default to `auth=None`. For HTTP transports, this means the MCP endpoint accepts requests without bearer-token authentication, and components without authorization checks are accessible to anyone who can reach it.
Authentication default, from FastMCP's server authentication documentation, read 1 November 2026.

GitHub's advisory page for the CVE as it stood when read: why no automatic alert should be expected from it.

Text
Unreviewed
Package No package listed
Affected versions Unknown
Patched versions Unknown
Dependabot alerts are not supported on this advisory because it does not have a package from a supported ecosystem with an affected and fixed version.
Status lines on GitHub advisory GHSA-3g72-rrqc-f76c, from the GitHub Advisory Database page, read 1 November 2026.

The exploitation check. This is this site's own search of CISA's catalogue file, so it is marked derived; it will be out of date as soon as CISA adds an entry.

Text
catalogVersion: 2026.10.30
dateReleased: 2026-10-30T17:02:45.3665Z
count: 1766
CVE-2026-120338 listed: no  (derived: searched by this site, read 21:38 GMT, 1 November 2026)
CISA Known Exploited Vulnerabilities catalogue file, searched by this site at 21:38 GMT on 1 November 2026.

The question to ask

Prefab's documentation said the sandbox had no write access while its launcher passed --allow-write, and the two sat side by side for 213 days in a project with a public repository and tests. Most stacks that run model written code now have something called a sandbox in them. For the one in yours, has anyone compared what the documentation promises with the flags the process is actually started with, and who would notice if they differed?

Key facts

Sources

  1. PrimaryCVE-2026-120338 record: title, description, CWEs, two CVSS 3.1 scenarios, affected range and files, credits, timeline including the 5 August report and the note that no advisory text shipped with 0.21.0CVE Program (CNA: JFrog)accessed 2026-11-01
  2. PrimaryGHSA-3g72-rrqc-f76c, GitHub's copy of the CVE: unreviewed, no package listed, affected and patched versions unknown, Dependabot alerts not supported, CVSS 10.0GitHub Advisory Databaseaccessed 2026-11-01
  3. PrimaryPull request 538, 'Refresh dependencies and fix renderer and sandbox regressions', merged 25 October 2026, the fix pull request named in the recordPrefectHQ (GitHub)accessed 2026-11-01
  4. PrimaryCommit 030f074: the launcher flags, the permission revocation in runner.js, and the documentation change; diff read in full for the sandbox filesPrefectHQ (GitHub)accessed 2026-11-01
  5. Primaryv0.21.0 release, 26 October 2026: the release notes and their description of the sandbox changePrefectHQ (GitHub)accessed 2026-11-01
  6. Primaryv0.14.0 release, 27 March 2026: the first release with the Pyodide sandbox (pull request 334)PrefectHQ (GitHub)accessed 2026-11-01
  7. PrimaryPull request 334, 'Add Pyodide sandbox for generative UI', merged 24 March 2026: the original launch flags and the 'safely execute' descriptionPrefectHQ (GitHub)accessed 2026-11-01
  8. Primaryprefab-ui 0.20.2 launcher source, _pyodide.py: the unrestricted Deno flags and the error raised when deno is not foundPrefectHQ (GitHub)accessed 2026-11-01
  9. Primaryprefab-ui 0.21.0 launcher source, _pyodide.py: the fixed flagsPrefectHQ (GitHub)accessed 2026-11-01
  10. PrimaryPrefab Generative UI documentation at 0.20.2: 'untrusted LLM output can't access the host system' and 'no write' accessPrefectHQ (GitHub)accessed 2026-11-01
  11. PrimaryPrefab Generative UI documentation as published now: permissions revoked after start up; WASM alone not a host security boundaryPrefectHQaccessed 2026-11-01
  12. PrimaryFastMCP Generative UI page: new in 3.2.0, the provider line, generate_prefab_ui, server side validation, the Deno requirement and its automatic install claimFastMCP (PrefectHQ)accessed 2026-11-01
  13. PrimaryFastMCP v3.2.0 release, 30 March 2026, listing GenerativeUIPrefectHQ (GitHub)accessed 2026-11-01
  14. PrimaryFastMCP server authentication page: servers default to auth=None; HTTP transports onlyFastMCP (PrefectHQ)accessed 2026-11-01
  15. PrimaryFastMCP authorization page: per tool checks such as require_scopes; STDIO skips auth checksFastMCP (PrefectHQ)accessed 2026-11-01
  16. Primaryfastmcp-slim pyproject.toml on main, read 1 November 2026: apps extra requires prefab-ui>=0.18.0PrefectHQ (GitHub)accessed 2026-11-01
  17. PrimaryAbout the GitHub Advisory Database: Dependabot does not create alerts for unreviewed advisoriesGitHub Docsaccessed 2026-11-01
  18. PrimaryPython site module documentation: lines in .pth files starting with import are executed at start up; .start files in 3.15Python Software Foundationaccessed 2026-11-01
  19. PrimaryKnown Exploited Vulnerabilities catalogue file, version 2026.10.30, 1,766 entries, searched at 21:38 GMT on 1 November 2026: CVE-2026-120338 not listedCISAaccessed 2026-11-01

Share this briefing

Know someone who owns this problem? Send it to them.

Related briefings

The briefing, in your inbox

Practitioner analysis of cyber and AI security news. No vendor noise.

How often

Every new briefing in one email, at 7am, or at 7am, 12:30pm and 6pm. Nothing is sent when nothing is new. Unsubscribe any time.