TP-EXP-2026-0014 CVE-2026-39987 critical Patched AI Draft

Marimo Pre-Authentication Remote Code Execution via Terminal WebSocket (CVE-2026-39987)

CVE CVE-2026-39987 Platform Marimo < 0.23.0 Type Remote Code Execution
Severity CRITICAL
Status Patched
Zero-Day No
Disclosed April 8, 2026
Patched April 8, 2026
Days in the Wild 0
CISA KEV Listed

Severity Assessment

  • Exploitability: 9.7/10
  • Impact: 8.8/10
  • Weaponization Risk: 9.7/10
  • Patch Urgency: 9.5/10
  • Detection Coverage: 5.0/10

Summary

CVE-2026-39987 is a pre-authentication remote code execution vulnerability in marimo, an open-source reactive Python notebook platform with approximately 20,000 GitHub stars. The vulnerability resides in the terminal WebSocket endpoint /terminal/ws, which spawns a full interactive PTY shell without performing any authentication check on the connecting client.

The root cause is a missing call to validate_auth() in the terminal endpoint handler. All other WebSocket endpoints in the marimo server correctly invoke validate_auth() before accepting connections. The terminal endpoint checks only whether the server is running in edit mode and whether the platform supports PTY before accepting the connection and calling pty.fork(). An unauthenticated attacker with network access to a marimo instance can obtain a persistent interactive shell running with the privileges of the marimo server process using a single WebSocket connection.

The Sysdig Threat Research Team observed the first exploitation attempt in the wild 9 hours and 41 minutes after the GitHub security advisory was published, with no public proof-of-concept code in circulation at the time. The attacker exfiltrated an .env file containing credentials within 13 minutes of initial access. CISA added CVE-2026-39987 to the Known Exploited Vulnerabilities catalog on 2026-04-23 with a required action deadline of 2026-05-07.

The vulnerability affects marimo versions prior to 0.23.0. The fix was released in version 0.23.0 via pull request #9098.

Exploit Chain

Stage 1: Identify Exposed Marimo Instance

The attacker scans for internet-accessible marimo instances, which by default listen on port 2718 or 8080. The marimo web UI is publicly visible and fingerprinted through its HTML page title and JavaScript bundle paths. No authentication is required to reach the vulnerable endpoint.

GET / HTTP/1.1
Host: target:2718

HTTP 200 — marimo application HTML served
Instance confirmed as marimo, endpoint /terminal/ws reachable

Stage 2: Establish Unauthenticated WebSocket Connection to /terminal/ws

The attacker opens a WebSocket connection to /terminal/ws. The server-side handler (marimo/_server/api/endpoints/terminal.py) checks only app_state.mode != SessionMode.EDIT and not supports_terminal(). Neither check involves authentication. The connection is accepted and pty.fork() is called, creating a child process with a pseudo-terminal.

# Vulnerable handler (pre-0.23.0)
@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 validate_auth() call — connection accepted unconditionally
    await websocket.accept()
    child_pid, fd = pty.fork()   # PTY shell created without auth

Compare with the correct pattern in the /ws endpoint:

# Correctly protected endpoint
validator = WebSocketConnectionValidator(websocket, app_state)
if not await validator.validate_auth():
    return

Stage 3: Execute Validation Sequence

The attacker confirms code execution with a scripted marker sequence to verify the shell is live before investing time in reconnaissance.

echo '---POC-START---'
id
echo '---POC-END---'

---POC-START---
uid=1000(marimo) gid=1000(marimo) groups=1000(marimo)
---POC-END---

The use of structured markers and id as the test command indicates an automated PoC script rather than manual interaction. The session lasted approximately 9 seconds.

Stage 4: Manual Reconnaissance

The attacker reconnects and performs manual filesystem exploration to map the environment and locate high-value files.

pwd         /app/marimo
whoami      marimo
ls          marimo  logs  data  .env  docker-compose.yml  celerybeat-schedule

The presence of .env and docker-compose.yml in the directory listing is the primary indicator that credentials are accessible.

Stage 5: Credential Exfiltration

The attacker reads the .env file and any other configuration files containing plaintext credentials. In the Sysdig-observed session this step completed within 13 minutes of initial access.

cat .env

DATABASE_URL=postgresql://user:password@db:5432/appdb
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
OPENAI_API_KEY=sk-...

Notebook deployments are common targets for this pattern because they are frequently configured with database connections, cloud provider credentials, and AI API keys as part of their operational workflows.

Detection Guidance

Detection RuleBehavioral IndicatorConfidence
Unauthenticated WebSocket to /terminal/wsWebSocket Upgrade request to path /terminal/ws with no valid auth cookie or token presentHIGH
PTY shell process spawned by marimoUnexpected shell processes (sh, bash, zsh) with marimo Python process as parentHIGH
Credential file access after WebSocket connectRead of .env, docker-compose.yml, or *_secret, *_token files within seconds of new WebSocket connectionHIGH
PoC marker strings in WebSocket trafficStrings ---POC-START--- or ---POC-END--- in WebSocket frame payloadsHIGH
Port 2718 probing followed by WebSocket upgradeExternal IP probing marimo default port then immediately issuing WebSocket Upgrade to /terminal/wsMED
Outbound connections from marimo processUnexpected outbound HTTP or HTTPS connections from the marimo server process to non-standard destinationsMED

Network detection should focus on WebSocket upgrade requests to /terminal/ws on marimo application ports. Any connection from a client with no authenticated session cookie or Authorization header to this path is an exploit attempt.

For host-based detection, monitor for pty.fork() system calls or unexpected /bin/sh child processes spawned from the marimo Python process. In container environments, watch for file reads of .env or docker-compose.yml shortly after a new WebSocket connection is established.

In access logs, a WebSocket upgrade to /terminal/ws with no preceding authentication event in the session is the primary detection signal.

Indicators of Compromise

TypeIndicatorContext
IP49.207.56.74Attacker IP observed in Sysdig honeypot exploitation, Apr 9 2026
String---POC-START---WebSocket terminal payload; PoC validation marker
String---POC-END---WebSocket terminal payload; PoC validation marker
Commandecho '---POC-START---'; id; echo '---POC-END---'Scripted exploit validation sequence
Path/terminal/wsVulnerable WebSocket endpoint; unauthenticated access is an exploit indicator
ProcessShell child of marimo Python processPTY shell spawned from marimo server indicates exploitation
File Access.env read by marimo processCredential exfiltration pattern observed in active exploitation

Disclosure Timeline

2026-04-08 — GitHub Security Advisory GHSA-2679-6mx9-h9xc Published

The marimo team published security advisory GHSA-2679-6mx9-h9xc disclosing the pre-authentication RCE in the /terminal/ws terminal WebSocket endpoint. The advisory described the missing validate_auth() call, compared the vulnerable endpoint to correctly protected endpoints, and included the code path for pty.fork(). The patch was released in marimo 0.23.0 via pull request #9098.

2026-04-09 — First Exploitation Observed (9h 41m After Disclosure)

At 07:31 UTC, the Sysdig Threat Research Team detected the first exploitation attempt against their honeypot fleet from IP 49.207.56.74. At 07:44 UTC, the attacker exfiltrated the .env file. At 08:57 UTC, the same attacker returned for a second exploitation session. No public proof-of-concept code existed at the time. The attacker constructed a working exploit directly from the advisory description.

2026-04-09 — Sysdig Threat Research Team Analysis Published

Sysdig published a technical analysis of the active exploitation, documenting the attacker’s session commands, timeline, and credential theft methodology. The analysis confirmed no public PoC was available at the time of the first attack and assessed that threat actors monitoring advisory feeds are capable of weaponizing vulnerabilities in niche software within hours of disclosure.

2026-04-23 — CISA Adds CVE-2026-39987 to Known Exploited Vulnerabilities Catalog

CISA added CVE-2026-39987 to the KEV catalog with a required action deadline of 2026-05-07. Federal agencies under BOD 22-01 are required to apply vendor mitigations or discontinue use of affected marimo versions by that date. The NVD entry was published the same day with a CVSS v3.1 base score of 9.8 Critical and CVSS v4.0 score of 9.3 Critical.

Sources & References