
Proof-of-concept exploit for CVE-2026-39987, a pre-authentication RCE in Marimo's /terminal/ws WebSocket endpoint that yields an interactive shell without credentials.
CVSS 4.0: 9.3 (Critical) · AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H
Affects: marimo <= 0.20.4 · Patched: 0.23.0 · CWE-306 (Missing Authentication)
Advisory: GHSA-2679-6mx9-h9xc · Fix: marimo-team/marimo@c24d480
The WebSocket endpoint /terminal/ws (marimo/_server/api/endpoints/terminal.py)
only checks the session mode (EDIT) and platform support before accepting the
connection and calling pty.fork(). It carries neither a @requires(...)
decorator nor a validate_auth() call.
Contrast measured in 0.20.4:
@requires(...) across 18 endpoints./ws (notebook) -> 2 calls to .validate_auth()terminal.py -> 0 @requires, 0 validate_auth. <-- the flaw.The AuthBackend middleware protects HTTP and the /ws websocket, but
/terminal/ws accepts the connection with no cookie and no token and forks a
PTY shell with the privileges of the marimo process.
marimo edit ...), which is the editor's default mode.pty support (Linux/macOS; Windows and Pyodide are not affected).An attacker who can reach a marimo edit server over the network (common in
remote / container deployments, often running as root) gets an interactive
shell without authenticating: credential theft (cat .env), lateral movement,
malware deployment. Exploited in the wild < 10 h after disclosure; added to
CISA's KEV catalog on 2026-04-23.
pip install "marimo==0.20.4" websockets
marimo edit demo.py --headless --port 2718 --token --token-password SECRET
python exploit.py ws://127.0.0.1:2718 "id; cat /etc/passwd"
exploit.py sends no token, no cookie and no Authorization header. It sends the
command as raw text + "\r" (Enter), exactly as a user would type it in the terminal.
/ws -> 403 (auth DOES work)./terminal/ws WITHOUT token -> ACCEPTED. Executed on the server:
uid=1000() gid=1000() ...
Linux ...
and wrote /tmp/cve_2026_39987_proof.txt -> RCE confirmed.Upgrade to marimo >= 0.23.0. Temporary mitigation: do not expose marimo edit
to untrusted networks; put the editor behind an authenticated proxy; run it
unprivileged / in a container without secrets.
For authorized security testing and educational use only. Run it only against systems you own or have explicit permission to test.