tl;dr
CVE-2026-39987 is a critical pre-authentication remote code execution vulnerability in Marimo, a popular Python reactive notebook framework (~19.6k GitHub stars). CVSS v4.0: 9.3 (Critical). Marimo is a modern alternative to Jupyter for reactive, Python-centric notebooks. Teams use it heavily in data science, ML experimentation, and internal analytics, often in containers with network access for collaboration.
This issue is fundamentally a WebSocket security failure: Marimo’s integrated terminal was reachable over a WebSocket endpoint (/terminal/ws) that did not enforce authentication, while other WebSockets on the same server did. A remote attacker only had to complete a WebSocket handshake without any authentication or interaction to get a full interactive shell as the user running the Marimo process. The bug is fixed in Marimo 0.23.0.
This vulnerability is confirmed to be exploited in the wild. Sysdig Threat Research Team observed the first exploitation attempt within 9 hours and 41 minutes of advisory publication, with a complete credential theft operation executed in under 3 minutes.
Based on our independent reconnaissance against large internet datasets, including services fronted on common HTTP ports, not only the default port used by Marimo, we believe tens, or perhaps hundreds, of instances may still be exposed and vulnerable if not patched.
If you run Marimo in edit mode anywhere it could be reached beyond a tightly trusted network, upgrade immediately, verify WebSocket routes are authenticated consistently, and treat notebook servers as high-value targets.
Affected versions
* Note: The advisory body text still states "Marimo <= 0.20.4" as affected, but the structured version range in the affected-packages section (consumed by Dependabot, pip audit, OSV, etc.) has been corrected to < 0.23.0. All versions prior to 0.23.0 are vulnerable.
Fixed version
Marimo closed the authentication gap on the terminal WebSocket in 0.23.0, aligning that path with validation used for other WebSockets. Upgrade with:
pip install --upgrade "marimo>=0.23.0"Official advisory (version bounds and details): https://github.com/marimo-team/marimo/security/advisories/GHSA-2679-6mx9-h9xc
Introduction
Marimo is a popular Python reactive notebook framework (on the order of 20k GitHub stars), a modern alternative to Jupyter for reactive, Python-centric notebooks. Teams use it heavily in data science, ML experimentation, and internal analytics, often in containers with network access for collaboration.
Marimo’s documentation highlights remote sharing and interactive editing as attractive features. That pushes real deployments toward internet-reachable edit servers, which can be easily accessed by attackers. The security reality is harsh: those hosts commonly sit next to sensitive data, environment files, cloud credentials, databases, and internal APIs.
CVE-2026-39987 shows what happens when one sensitive capability (a terminal backed by a WebSocket) is wired up without the same authentication discipline as the rest of the server’s WebSocket surface.
This problem sits in a wider pattern: powerful notebook and orchestration tools that were designed for trusted networks but end up exposed, in whole or in part, to the internet. Marimo’s flaw is especially dangerous because the attack is a single step at the WebSocket layer, i.e., no phishing, no stolen session, no supply-chain trick, just connect and run commands.
The vulnerability
Background: inconsistent WebSocket authentication
Marimo exposes multiple WebSocket endpoints. The primary session WebSocket path uses WebSocketConnectionValidator.validate_auth() so that only authorized clients keep a session.
The terminal WebSocket endpoint followed a different implementation path. Per the advisory, marimo/_server/api/endpoints/terminal.py accepted connections after checking edit mode and platform support for a PTY, but did not perform the same authentication validation used elsewhere.
Stated plainly: one WebSocket route was protected; another WebSocket route that emulates a shell was not. From an attacker’s perspective, the server advertises a normal authenticated WebSocket model, while a parallel WebSocket URL quietly grants a shell.
Why middleware was not enough
Marimo uses Starlette’s AuthenticationMiddleware. As described in the advisory, that layer can mark connections as unauthenticated without rejecting WebSocket upgrades outright. Effective protection depends on endpoint-level enforcement: validate_auth(), decorators, or equivalent. The terminal WebSocket path skipped that enforcement, so unauthenticated clients could still reach websocket.accept() and the subsequent pty.fork() path.
HTTP habits versus WebSocket reality
Authenticating a bidirectional WebSocket channel is fundamentally different than authenticating a normal HTTP request, which may be why mistakes like this slip through code review. REST-style handlers are usually short-lived: each request carries headers and cookies in a familiar place, and middleware or route guards run in lockstep with that request. A WebSocket begins as an HTTP upgrade, but after the handshake, the connection becomes a long-lived, full-duplex pipe; identity and authorization must be established at upgrade time (or re-checked explicitly on sensitive messages), and it is easy to assume that “the app is already authenticated” once any session exists elsewhere. Different frameworks surface that upgrade path differently, so one endpoint can accidentally skip the same checks another WebSocket route applies, exactly the failure mode seen here.
Attack chain (high level)
1. The attacker opens a WebSocket to ws://<host>:<port>/terminal/ws (or wss:// behind TLS).
2. The server accepts the WebSocket without credentials.
3. The server allocates a PTY and a shell.
4. The attacker runs arbitrary commands as the Marimo process, commonly root in default Docker images.
The advisory contains a full proof of concept, including a scenario where the server issues an access_token for normal users; the terminal WebSocket path bypasses that model anyway.
Exploitation in the wild and exposed footprint
CVE-2026-39987 is confirmed to be exploited in the wild. Sysdig Threat Research Team deployed honeypot nodes running vulnerable Marimo instances across multiple cloud providers and observed exploitation within hours of disclosure.
The vulnerability is pre-authentication, requires only a WebSocket client, and the advisory itself documents a working exploit. That combination typically outpaces patch adoption, especially for internal data-science infrastructure that is not centrally patched like front-line web apps.
Internet-wide exposure is easy to underestimate. Marimo is frequently served behind reverse proxies or TLS on ordinary HTTP(S) ports, so a quiet signal on any one default port is not a reliable proxy for “not on the internet.” In practice, the product’s emphasis on remote sharing and interactive editing pushes many real deployments toward browser-reachable edit servers—the same posture where an unauthenticated terminal channel is most dangerous.
Post-disclosure, we measured that exposure directly—without touching victim systems beyond the handshake. Against a sample of 186 internet-reachable Marimo base URLs, we attempted only an unauthenticated WebSocket upgrade to /terminal/ws. Thirty (30) connections completed successfully—about 16% of the sample. We deliberately did not execute commands or otherwise interact with shells on those third-party hosts, because doing so would cross into unauthorized access and create legal risk for good-faith research. Even under that constraint, a completed upgrade is the same technical step an attacker takes immediately before running the first command, so it is a meaningful signal of exploitability for unpatched versions.
Those thirty endpoints are not generic “Marimo on the internet” noise. In Marimo’s server code path for the integrated terminal (the same surface described in the advisory), the terminal WebSocket is only available when the notebook server is running in edit mode; configurations that only serve read-only or static notebook output do not expose that upgrade path in the same way. So every successful handshake in our sample is strong evidence of an edit-mode deployment that is willing to complete the vulnerable terminal WebSocket, modulo any additional network or application controls we could not see.
Thirty is a lower bound, not an upper bound. The denominator is a bounded crawl sample, not the entire internet; the numerator counts successful handshakes (including multiple URLs that map to the same host on different ports). It excludes private networks or hosts behind authentication or corporate egress.
Regardless of the exact number, any unpatched, internet-reachable Marimo instance in an editing posture should be treated as a severe exposure until upgraded and until WebSocket authentication is verified consistently across every sensitive route.
Blast radius: Colocated services amplify impact
Internet-facing Marimo instances are commonly colocated with other high-value services on the same host — AI/ML orchestration platforms, LLM front-ends, source code repositories, remote access tools, and reverse proxy infrastructure. If exploited, an attacker would likely find themselves on cloud VMs alongside production `.env` files containing cloud provider credentials.
Compromising Marimo on these hosts gives lateral access to every co-located service. In some cases, a single WebSocket connection is all that separates an attacker from cloud account takeover via exposed credentials on disk.
Threat model mismatch
If your product story encourages sharing and remote access, your security story must assume that every WebSocket and every debug or terminal feature is part of the attack surface. CVE-2026-39987 is a textbook example of asymmetric trust: users believe “the server requires auth,” while one WebSocket URL did not. Additionally, developer tools and applications with sensitive functionality like code and script execution require a greater deal of scrutiny and segmentation within an environment. Such functionality increases the impact of vulnerabilities like authentication bypass to system compromise.
Impact
The impact matches the worst case for pre-auth RCE:
1. Remote code execution with no authentication and low attack complexity.
2. Data exfiltration from disk and memory: secrets, notebooks, SSH keys, cloud tokens.
3. Lateral movement: notebook hosts often reach databases, object stores, internal HTTP services, and peer systems.
4. Persistence is limited only by the attacker's goals and your defensive controls.
Combined with confirmed in-the-wild exploitation, unpatched exposure should be treated as an active incident risk, not a theoretical CVE line item.
Mitigations
1. Patch immediately to 0.23.0 or newer.
2. Do not expose Marimo edit mode to untrusted networks. Prefer VPNs, private subnets, authenticated proxies, and allowlists.
3. Avoid binding to 0.0.0.0 unless network controls are explicit and verified.
4. Container hardening: non-root users, read-only roots where practical, minimal capabilities—while remembering that pre-auth RCE often still means game over; layer defenses.
5. Secrets hygiene: treat notebook servers as secret-adjacent; avoid shipping production .env files on shared lab hosts; rotate credentials if exposure is possible.
6. Detection: monitor WebSocket connections to terminal paths from unexpected clients; alert on new shells, odd process trees, and unusual egress from notebook infrastructure.
7. Assume in-the-wild abuse: hunt for post-exploitation artifacts even if you did not see the WebSocket probe itself in logs.
Takeaways for developers and security teams
1. Every WebSocket route must be explicitly enrolled in your authentication and authorization model. Middleware alone is not a WebSocket security strategy; treat the upgrade and the ongoing channel as distinct from “just another HTTP route.”
2. WebSocket upgrades are easy to forget in code review because they look “like streaming HTTP.” Treat them like new listening services.
3. Power features (terminals, REPLs, kernels) need strict gates: strong auth, explicit enablement, least privilege.
4. Sharing and remote editing change the threat model; document secure defaults as loudly as product benefits.
5. When a pre-auth RCE drops with a public PoC, plan for in-the-wild use on day one; patch and contain faster than your mean time to read a CVE feed.
Conclusion
CVE-2026-39987 is a critical pre-authentication remote code execution in Marimo: a single unauthenticated WebSocket to `/terminal/ws` yields a full shell. This vulnerability is confirmed to be exploited in the wild, and our hands-on checks on 186 URLs found 30 (~16%) accepting an unauthenticated /terminal/ws handshake, so this is a lower bound on at-risk edit servers.
If you run Marimo, upgrade to 0.23.0 or later now, audit WebSocket endpoints for consistent auth, and assume any internet-adjacent notebook editor is on an attacker's short list.
References
- GitHub Security Advisory (CVE-2026-39987): https://github.com/marimo-team/marimo/security/advisories/GHSA-2679-6mx9-h9xc
- Sysdig TRT - From Disclosure to Exploitation in Under 10 Hours: https://www.sysdig.com/blog/marimo-oss-python-notebook-rce-from-disclosure-to-exploitation-in-under-10-hours
- Marimo security documentation: https://docs.marimo.io/security/
- CWE-306 - Missing Authentication for Critical Function: https://cwe.mitre.org/data/definitions/306.html
Give Your AI Coding Assistants the Security Tools They Deserve



What's next?
When you're ready to take the next step in securing your software supply chain, here are 3 ways Endor Labs can help:
.jpg)










