# Marimo CVE-2026-39987: unauthenticated /terminal/ws RCE exploited to harvest AWS credentials and pivot to an SSH bastion in 8 seconds

> CVE-2026-39987 is a pre-authentication RCE in the marimo Python notebook: the /terminal/ws WebSocket skips authentication and hands an attacker a full PTY shell. Sysdig documented a human operator using hand-coded boto3 tooling (no LLM involvement) to steal AWS credentials from environment variables and the app's Redis backend, pull SSH keys from Secrets Manager, and log in to an SSH bastion 8 seconds after opening the WebSocket.

- **Published:** 2026-11-06T00:45:29Z
- **Last reviewed:** 2026-11-06T00:45:29Z
- **Canonical:** https://intel.threadlinqs.com/threat/TL-2026-5360
- **Card image:** https://intel.threadlinqs.com/og/threat/TL-2026-5360.png
- **ID:** TL-2026-5360
- **Severity:** CRITICAL (CVSS 9.8)
- **Category:** VULNERABILITY
- **Status:** ACTIVE
- **Detections:** 9 (1 shown free below) · **IOCs:** 15 (3 shown free below)
- **CVEs:** CVE-2026-39987

## Description

marimo is a reactive Python notebook. In versions up to and including 0.20.4, the /terminal/ws WebSocket endpoint does not call validate_auth(), unlike the /ws endpoint. It only checks the running mode and platform support. An unauthenticated client can connect to ws://TARGET:2718/terminal/ws and receive a full PTY shell as the marimo process user (the advisory PoC runs 'id' and returns root). The flaw is CWE-306 (Missing Authentication for Critical Function), CVSS 3.1 9.8 per NVD (vendor/CNA CVSS 4.0 9.3), and was fixed in marimo 0.23.0 (PR #9098). GHSA-2679-6mx9-h9xc was published 2026-04-08 (reporter @q1uf3ng) and no workaround is documented in the advisory. CISA added the CVE to KEV on 2026-04-23 with a federal due date of 2026-05-07.

Sysdig Threat Research observed the first exploitation attempt 9 hours 41 minutes after the advisory was published. The attacker built the exploit from the advisory text and manually explored the host. Between 2026-04-11 and 2026-04-14, 662 exploit events from 11 unique source IPs in 10 countries were recorded. One of these, 38.147.173.172, used curl to deploy a Go-based NKAbuse ELF variant disguised as 'kagent', fetched via a shell script hosted on a Hugging Face Space ('vsccode-modetx'). NKAbuse persisted through a systemd user service, crontab, or a macOS LaunchAgent, and uses the NKN blockchain for P2P C2 (with WebRTC/STUN proxying) and supports DDoS and remote command execution.

A second Sysdig report, 'Machine speed, hold the AI', covers a single hand-coding operator. The operator connected from 172.236.12.17 (Akamai/Linode London, AS63949) and began with a /dev/tcp sweep of an RFC1918 /24. They harvested AWS credentials from environment variables/credential files and from the application's Redis backend (a GET on an encrypted-credential key), and validated both credentials, mapped to two distinct IAM users, with sts GetCallerIdentity. The first credential was validated 28+ hours before terminal activity and the second 33 minutes after it. The operator deployed base64-encoded Python scripts to /tmp (echo '<b64>' | base64 -d > /tmp/<name>.py; nohup python3 ... &), installed boto3 with pip3 when missing, and ran region-fallback secretsmanager:GetSecretValue calls across us-east-1, us-west-2, eu-west-1, ap-southeast-1 and us-east-2, with ListSecrets as an AccessDenied fallback. The retrieved SSH private key was persisted to /tmp/bastion_key (mode 0600) and replayed against root@45.79.187.72:22 (Akamai/Linode Cedar Knolls, NJ), trying RSA, Ed25519 and ECDSA key types. The operator also ran a stdlib-only Python reverse shell and an nc -lvp 4444 listener on that VPS. When EC2 enumeration (DescribeInstances, DescribeKeyPairs, DescribeInstanceInformation) was denied, automation fired ec2:SendSSHPublicKey against the placeholder instance i-0000000000000000, which is a distinctive behavioral fingerprint. A later session went from WebSocket open to Secrets Manager call in 14 seconds and to SSH bastion authentication in 8 seconds.

Sysdig planted a natural-language prompt-injection trap in the container that had caught every LLM-driven operator profiled earlier against this CVE. This operator read the file twice and never echoed the marker, which Sysdig takes as evidence of human, non-LLM tradecraft. No named threat actor or nation-state attribution is made in the sources.

Detection (Sysdig): chain /proc/self/environ or .env reads by a process, then HTTPS to secretsmanager.<region>.amazonaws.com, then outbound TCP to a non-RFC1918 address on a non-standard port, all within one minute. In CloudTrail, GetSecretValue from a new principal with AccessDenied and region-fallback retries inside a minute is a high-fidelity pattern. GuardDuty UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS flags credential replay from outside AWS. Note: BeaconBeagle lookups for the two network IOCs returned HTTP 502 at research time, so no C2 correlation was obtained.

## MITRE ATT&CK

- T1190 Exploit Public-Facing Application
- T1059.004 Unix Shell
- T1059.006 Python
- T1552.001 Credentials In Files
- T1555.006 Cloud Secrets Management Stores
- T1046 Network Service Discovery
- T1526 Cloud Service Discovery
- T1140 Deobfuscate/Decode Files or Information
- T1021.004 SSH
- T1098.004 SSH Authorized Keys
- T1078.004 Cloud Accounts
- T1571 Non-Standard Port
- T1543.002 Systemd Service
- T1053.003 Cron

## Free detection (Splunk SPL, Microsoft KQL, Sigma)

**Marimo CVE-2026-39987 Unauthenticated /terminal/ws PTY WebSocket Access — Exploitation** (`TL-2026-5360-SPL-001`)

Detects requests to the marimo /terminal/ws WebSocket endpoint (CVE-2026-39987, CWE-306) in web, proxy or load-balancer logs. The endpoint skips validate_auth() and hands any client a full PTY shell as the marimo process user, so any external access to it is exploitation or probing. Flags known attacker sources 172.236.12.17 and 38.147.173.172 and counts successful 101 upgrades.

- **Languages:** Splunk SPL, Microsoft KQL, Sigma
- **ATT&CK:** T1190, T1059.004
- **Data sources:** web:access_combined, web:nginx:plus:kv, aws:elb:accesslogs
- **Severity:** critical
- **Confidence:** high

### Splunk SPL

````spl
index=web (sourcetype=access_combined OR sourcetype="nginx:plus:kv" OR sourcetype="aws:elb:accesslogs") (uri_path="/terminal/ws" OR uri_path="/terminal/ws/*") earliest=-24h ```CVE-2026-39987 marimo unauthenticated PTY WebSocket```
| eval ws_upgrade=if(status=101,1,0)
| eval known_bad=if(in(src_ip,"172.236.12.17","38.147.173.172","45.79.187.72"),"yes","no")
| stats count AS requests sum(ws_upgrade) AS ws_upgrades min(_time) AS first_seen max(_time) AS last_seen values(dest) AS dest values(http_user_agent) AS user_agents by src_ip known_bad
| convert ctime(first_seen) ctime(last_seen)
| sort - ws_upgrades
````

### Microsoft KQL

```kql
CommonSecurityLog
| where TimeGenerated > ago(24h)
| where RequestURL contains "/terminal/ws"
| extend KnownBadSource = SourceIP in ("172.236.12.17", "38.147.173.172", "45.79.187.72")
| summarize Requests = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Urls = make_set(RequestURL, 10), Agents = make_set(RequestClientApplication, 5) by SourceIP, DestinationIP, DeviceVendor, KnownBadSource
| project FirstSeen, LastSeen, SourceIP, DestinationIP, Requests, KnownBadSource, Urls, Agents, DeviceVendor
```

### Sigma

```yaml
title: Marimo Unauthenticated /terminal/ws WebSocket Access (CVE-2026-39987)
id: 7c1f2a4e-5b3d-4e8a-9c61-2d0a7e3b5f11
status: experimental
description: Detects access to the marimo /terminal/ws WebSocket endpoint, which lacks authentication in marimo <= 0.20.4 and returns a PTY shell (CVE-2026-39987).
references:
  - https://github.com/marimo-team/marimo/security/advisories/GHSA-2679-6mx9-h9xc
  - https://www.sysdig.com/blog/machine-speed-hold-the-ai-hand-rolled-marimo-cve-2026-39987-exploit
  - https://nvd.nist.gov/vuln/detail/CVE-2026-39987
author: AII-Detector — ThreadLinqs Intelligence
date: 2026-11-06
tags:
  - attack.initial-access
  - attack.t1190
  - attack.execution
  - attack.t1059.004
  - cve.2026-39987
logsource:
  category: webserver
detection:
  selection:
    cs-uri-stem|contains: '/terminal/ws'
  condition: selection
falsepositives:
  - Authorized internal users of a patched (>= 0.23.0) marimo terminal behind authentication
  - Vulnerability scanners validating CVE-2026-39987
level: critical
```

**Known false positives:**

- Authorized data scientists using the marimo terminal on a patched (>= 0.23.0) instance behind authenticated access
- Internal vulnerability scanners or exposure-management tools probing /terminal/ws for CVE-2026-39987

**Tuning notes:** Adjust the index and sourcetype list to where marimo reverse-proxy logs land and make sure uri_path is extracted. Exclude scanner source ranges and the corporate VPN egress. If the proxy terminates WebSockets and logs 200 rather than 101, drop the status test. Any hit from a public source against an unpatched host (marimo <= 0.20.4) should be treated as compromise and trigger credential rotation.

### 8 more detection rules (Splunk SPL, Microsoft KQL, Sigma), unlock with TL Creds

- NKAbuse '[indicator]' Delivery via curl From Hugging Face Space After Marimo RCE (locked: unlock with TL Creds)
- Credential Harvesting From /proc/self/environ, .aws/credentials and .env, Stolen Key Written to [indicator] (locked: unlock with TL Creds)
- Outbound Connection to Marimo Attacker Infrastructure, Reverse-Shell Ports or SSH From Python — C2 / Lateral Movement (locked: unlock with TL Creds)
- NKAbuse Persistence via systemd User Service or Crontab After Marimo Compromise (locked: unlock with TL Creds)
- /dev/tcp Subnet Sweep, Netcat Listener on 4444 and Python Reverse Shell — Discovery and C2 Staging (locked: unlock with TL Creds)
- EC2 Enumeration Denied Then ec2:SendSSHPublicKey Against Placeholder Instance i-0000000000000000 (locked: unlock with TL Creds)
- Marimo Post-Exploitation Base64-Staged Python Script in /tmp (chain.py / full_chain.py) (locked: unlock with TL Creds)
- Secrets Manager GetSecretValue Region-Fallback and AccessDenied Pattern — Stolen AWS Credential Use (locked: unlock with TL Creds)

Sign up free to unlock the detection rules: https://threadlinqs.com/?modal=signup

## Sample indicators (3 of 15)

Defanged, TLP:CLEAR.

- IP address: `172.236.12[.]17`
- Domain: `secretsmanager[.]us-east-1[.]amazonaws[.]com`
- IP address: `45.79.187[.]72`

Full set of 15 indicators unlocks with TL Creds: https://threadlinqs.com/tl-creds

## Sources

- [tl;dr sec #346](https://tldrsec.com/p/tldr-sec-346)
- [Sysdig: Machine speed, hold the AI - Hand-rolled marimo CVE-2026-39987 exploit](https://www.sysdig.com/blog/machine-speed-hold-the-ai-hand-rolled-marimo-cve-2026-39987-exploit)
- [Sysdig: Marimo OSS Python Notebook RCE from Disclosure to Exploitation in Under 10 Hours](https://sysdig.com/blog/marimo-oss-python-notebook-rce-from-disclosure-to-exploitation-in-under-10-hours)
- [GitHub Security Advisory GHSA-2679-6mx9-h9xc](https://github.com/marimo-team/marimo/security/advisories/GHSA-2679-6mx9-h9xc)
- [NVD CVE-2026-39987](https://nvd.nist.gov/vuln/detail/CVE-2026-39987)
- [CISA Known Exploited Vulnerabilities Catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
- [The Hacker News: Marimo RCE flaw CVE-2026-39987](https://thehackernews.com/2026/04/marimo-rce-flaw-cve-2026-39987.html)
- [Security Affairs: CVE-2026-39987 Marimo RCE exploited in hours after disclosure](https://securityaffairs.com/190623/hacking/cve-2026-39987-marimo-rce-exploited-in-hours-after-disclosure.html)
- [InfoWorld: Critical flaw in marimo Python notebook exploited within 10 hours of disclosure](https://www.infoworld.com/article/4157823/critical-flaw-in-marimo-python-notebook-exploited-within-10-hours-of-disclosure-2.html)

## Full data

The other 8 detection rules (Splunk SPL / Microsoft KQL / Sigma) unlock with TL Creds; a free account includes daily creds: https://threadlinqs.com/?modal=signup
Indicator values unlock with TL Creds, at a lower price on Red and Purple; AI agents can query rules and IOCs through the Threadlinqs MCP server (Purple): https://intel.threadlinqs.com/mcp

Canonical: https://intel.threadlinqs.com/threat/TL-2026-5360
