Apr 9, 2026·5 min read·67 visits
The marimo notebook environment prior to version 0.23.0 exposes an unauthenticated terminal WebSocket endpoint (`/terminal/ws`). Attackers can connect to this endpoint to spawn a PTY shell, executing arbitrary commands with the privileges of the marimo server process.
An authentication bypass vulnerability in the marimo interactive Python notebook environment allows unauthenticated remote attackers to obtain interactive pseudo-terminal (PTY) shell access. The flaw resides in the terminal WebSocket endpoint, which fails to enforce required authentication checks, leading to critical remote code execution capabilities on the host system.
The marimo application is a reactive notebook environment for Python that provides a web-based interface backed by a local server. This server exposes several HTTP and WebSocket endpoints to facilitate interactivity, code execution, and terminal access.
The vulnerability is located within the terminal-specific WebSocket endpoint at the /terminal/ws route. In default configurations, marimo restricts access to its interactive features using an authentication token system. The server relies on a specific validation function to enforce these checks across its various communication channels.
While the primary application WebSocket route correctly implements authentication validation, the terminal endpoint omits this critical step. It strictly verifies whether the application operates in "edit mode" and confirms underlying operating system support for pseudo-terminals (PTYs). Because the endpoint fails to establish the identity of the incoming connection, any client capable of reaching the open port can initiate a connection and gain complete access to the resulting terminal session.
The underlying technical flaw is an instance of CWE-306: Missing Authentication for Critical Function. The marimo backend utilizes the Starlette asynchronous framework for HTTP routing and middleware execution. In this architecture, an AuthenticationMiddleware component is responsible for parsing inbound requests and populating user identity information.
However, Starlette's middleware design dictates that WebSocket connections are not automatically terminated or rejected upon missing authentication tokens unless explicitly programmed to do so within the specific endpoint handler. The marimo application standardizes this process through a validate_auth() utility function, which inspects the WebSocket connection for a valid access_token query parameter or corresponding session cookie.
In marimo/_server/api/endpoints/terminal.py, the websocket_endpoint handler dictates the connection lifecycle for the terminal interface. The implementation checks the application mode (app_state.mode != SessionMode.EDIT) and evaluates PTY support but completely lacks the required validate_auth(websocket) invocation. Consequently, the handler bridges the inbound connection directly to the PTY subsystem without verifying the source's authorization state.
An analysis of the source code reveals the direct omission of the validation logic. The vulnerable implementation accepts the WebSocket object and immediately instantiates the AppState without authenticating the client session.
# Vulnerable implementation prior to 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(code=1008, reason="Terminal only available in edit mode")
return
# Execution proceeds to spawn the PTYThe fix, introduced in commit c24d4806398f30be6b12acd6c60d1d7c68cfd12a, rectifies this by explicitly invoking the missing authorization check before processing any connection state. If the authentication logic fails, the connection is safely terminated with an appropriate WebSocket status code.
# Patched implementation in 0.23.0
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket) -> None:
if not validate_auth(websocket):
await websocket.close(code=3000, reason="MARIMO_UNAUTHORIZED")
return
app_state = AppState(websocket)
if app_state.mode != SessionMode.EDIT:
await websocket.close(code=1008, reason="Terminal only available in edit mode")
returnThe following diagram illustrates the vulnerable connection flow prior to the patch application.
Exploiting this vulnerability requires network access to the target marimo server port, which defaults to 2718. The attacker begins by scanning for exposed instances or specifically targeting known deployment addresses.
Once a target is identified, the attacker crafts a standard WebSocket upgrade request targeting the /terminal/ws endpoint. The client intentionally omits any access_token query parameters or authentication cookies. The server receives the connection, processes the HTTP-to-WebSocket upgrade, and transitions into the handler logic.
The server confirms the SessionMode is set to edit, which is the typical operational state for an active notebook. It subsequently issues a pty.fork() system call, creating a new child process running the system's default shell executable. Standard input, output, and error file descriptors of the child process are routed to an asynchronous I/O loop that frames terminal data into WebSocket messages.
The attacker receives an interactive shell session. By transmitting appropriately framed WebSocket text messages containing shell commands, the attacker achieves arbitrary command execution on the target operating system.
This vulnerability achieves a CVSS v3.1 base score of 9.5 (Critical) due to the simplicity of the attack vector and the severity of the consequences. The vector evaluates to CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H.
The unauthenticated remote code execution occurs in the context of the user running the marimo server process. If marimo is executed by a standard user account on a developer workstation, the attacker gains full access to user files, environment variables, internal network resources, and adjacent credentials.
If the application is deployed in a server or containerized environment with elevated privileges, the compromise depth increases significantly. The absence of complex exploitation prerequisites renders this vulnerability highly reliable to exploit via automated tooling.
The primary remediation for this vulnerability is immediately upgrading the marimo Python package to version 0.23.0 or later. The patch completely prevents unauthenticated connections from reaching the PTY subsystem by enforcing the standard token verification mechanism.
If immediate patching is technically infeasible, administrators must restrict network access to the marimo instance. Bind the service exclusively to local loopback addresses (127.0.0.1 or localhost) and utilize a virtual private network or SSH tunneling for remote access. Do not expose the service to zero-route addresses (0.0.0.0) on untrusted networks.
Additionally, technical analysis indicates a secondary risk related to Cross-Site WebSocket Hijacking (CSWSH). If users interact with the patched marimo server through a browser that stores active session cookies, a malicious website visited by the user could attempt to initiate a WebSocket connection. Organizations should configure marimo to validate tokens strictly through query parameters and enforce strong SameSite cookie policies.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
marimo marimo-team | < 0.23.0 | 0.23.0 |
| Attribute | Detail |
|---|---|
| Bug Class | Improper Authorization (CWE-306) |
| Attack Vector | Network (WebSocket) |
| Authentication Required | None |
| Impact | Remote Code Execution (RCE) |
| CVSS v3.1 Score | 9.5 (Critical) |
| Exploit Maturity | Proof of Concept Available |
The software does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources.
A state desynchronization (cache drift) vulnerability exists in the multimodal Inter-Process Communication (IPC) Least Recently Used (LRU) caches of vLLM. When a multimodal request fails validation after its media hash has been registered on the frontend but before the payload is committed to the backend engine core, the frontend and backend caches drift out of lockstep. A subsequent request reusing the same media triggers an assertion failure in the backend engine core, resulting in a complete denial of service.
CVE-2026-105750 is a medium-severity local file disclosure vulnerability affecting the Docling and Docling-Slim libraries. When processing HTML documents using the optional Playwright rendering backend, the application fail to validate and restrict request URIs using the file:// scheme. This permits an attacker supplying a crafted HTML file to access, render, and exfiltrate local system files.
CVE-2026-102598 is a security bypass and Denial of Service (DoS) vulnerability in the Werkzeug WSGI web application library. In versions prior to 3.1.9, the library's safe_join function fails to sanitize Windows reserved device names containing an empty NTFS Alternate Data Stream (ADS) marker (such as NUL:). This allows remote, unauthenticated attackers to trigger indefinite thread-blocking operations on Windows hosts, resulting in application-wide resource exhaustion.
A vulnerability in @graphql-tools/executor-legacy-ws prior to version 1.1.35 hardcodes the TLS rejectUnauthorized setting to false for outgoing secure WebSocket (wss://) connections. This defect allows unauthenticated remote attackers to perform Adversary-in-the-Middle (MitM) attacks, capturing or tampering with sensitive connection payloads and subscription data.
CVE-2026-103918 is a medium-severity vulnerability within the @orpc/zod smart coercion plugin in oRPC. Prior to version 1.14.10, the package fails to sanitize untrusted input keys when performing pre-validation type coercion, allowing prototype injection on the returned request object and Denial of Service.
A weak path access control vulnerability in SiYuan note-taking application allowed authenticated users to bypass restricted file paths by requesting historical backups and git diffs of sensitive configurations, including plain-text authentication tokens.