Threat reportVulnerabilityTL-2026-5360
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. First disclosed .
Key facts for Marimo pre-auth shell
| Identifiers | TL-2026-5360, CVE-2026-39987 |
|---|---|
| Also known as | GHSA-2679-6mx9-h9xc |
| Severity | CRITICAL |
| CVSS | 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| First disclosed | |
| Exploitation status | Exploited in the wild; Listed in CISA KEV: CVE-2026-39987 (added ) |
| EPSS | CVE-2026-39987: 37.8% (98.5th percentile) |
| Affected products | marimo-team marimo |
| Weaknesses (CWE) | CWE-306 |
| Malware | NKAbuse - S1107 |
| Tools | Netcat, boto3 |
| MITRE ATT&CK techniques | T1021.004 SSH, T1046 Network Service Discovery, T1053.003 Cron, T1059.004 Unix Shell, T1059.006 Python, T1078.004 Cloud Accounts, T1098.004 SSH Authorized Keys, T1140 Deobfuscate/Decode Files or Information, T1190 Exploit Public-Facing Application, T1526 Cloud Service Discovery, T1543.002 Systemd Service, T1552.001 Credentials In Files and 2 more in the attack flow |
| Motivation | UNKNOWN |
| Target sectors | technology, data science and analytics, cloud-hosted development |
| Target regions | Global |
| Detection rules | 9 (1 shown free) |
| Sources | 9 cited sources |
Detection coverage for Marimo pre-auth shell
As of 2026-11-06, Threadlinqs Intelligence publishes 9 detection rule(s) for TL-2026-5360 across Splunk SPL, Microsoft KQL and Sigma, covering 15 indicator(s) of compromise. The whole corpus is readable without an account. Every threat comes with one complete detection in Splunk SPL, Microsoft KQL and Sigma, free for everyone; a free account adds daily TL Creds to unlock the rest. Paid plans add a larger daily allowance, correlation and the MCP server. Threadlinqs MCP server · View plans.
Free detection: Marimo CVE-2026-39987 Unauthenticated /terminal/ws PTY WebSocket Access — Exploitation
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
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_upgradesMicrosoft 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, DeviceVendorSigma
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: criticalKnown 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.
- NKAbuse '[indicator]' Delivery via curl From Hugging Face Space After Marimo RCE · unlock with TL Creds
- Credential Harvesting From /proc/self/environ, .aws/credentials and .env, Stolen Key Written to [indicator] · unlock with TL Creds
- Outbound Connection to Marimo Attacker Infrastructure, Reverse-Shell Ports or SSH From Python — C2 / Lateral Movement · unlock with TL Creds
- NKAbuse Persistence via systemd User Service or Crontab After Marimo Compromise · unlock with TL Creds
- /dev/tcp Subnet Sweep, Netcat Listener on 4444 and Python Reverse Shell — Discovery and C2 Staging · unlock with TL Creds
- EC2 Enumeration Denied Then ec2:SendSSHPublicKey Against Placeholder Instance i-0000000000000000 · unlock with TL Creds
- Marimo Post-Exploitation Base64-Staged Python Script in /tmp (chain.py / full_chain.py) · unlock with TL Creds
- Secrets Manager GetSecretValue Region-Fallback and AccessDenied Pattern — Stolen AWS Credential Use · unlock with TL Creds
Indicators of compromise for Marimo pre-auth shell
As of 2026-11-06, Threadlinqs Intelligence tracks 15 indicators of compromise for TL-2026-5360.
Sample indicators (3 of 15)
- IP address:
172.236.12[.]17 - Domain:
secretsmanager[.]us-east-1[.]amazonaws[.]com - IP address:
45.79.187[.]72
Defanged, TLP:CLEAR. Full set of 15 indicators: unlock with TL Creds.
How Marimo pre-auth shell works
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 techniques by tactic:
Lateral Movement
Discovery
T1046 Network Service Discovery; T1526 Cloud Service Discovery
Persistence
T1053.003 Cron; T1098.004 SSH Authorized Keys; T1543.002 Systemd Service
Execution
T1059.004 Unix Shell; T1059.006 Python
Initial Access
T1078.004 Cloud Accounts; T1190 Exploit Public-Facing Application
Defense Evasion
T1140 Deobfuscate/Decode Files or Information
Credential Access
T1552.001 Credentials In Files; T1555.006 Cloud Secrets Management Stores
Command and Control
Remediation for Marimo pre-auth shell
Patches
- marimo 0.23.0 (PR #9098)
Immediate actions
- Upgrade marimo to 0.23.0 or later
- Restrict /terminal/ws at the reverse proxy or disable the terminal feature; do not expose marimo (default port 2718) to the internet
- Rotate all credentials (AWS keys, Redis-stored credentials, SSH keys, .env secrets) on any internet-exposed marimo instance running since 2026-04-08
- Hunt /tmp on notebook hosts for chain.py, full_chain.py, bastion_key, relay_key, chain.log, callback.log and revshell.log
- Block 172.236.12.17, 45.79.187.72 and 38.147.173.172 and review historical traffic to them
Workarounds
- Disable or block the terminal WebSocket (/terminal/ws) at a reverse proxy and require network-level authentication in front of marimo
Longer-term hardening
- Block notebook-container egress except explicit needs (PyPI, model APIs) and block reverse-shell ports 4444, 1337, 1234, 8080, 9001, 31337
- Scope Secrets Manager IAM permissions to only the secrets required and avoid long-lived keys in environment variables or Redis
- Audit credential surfaces: process environment, systemd EnvironmentFile, ~/.aws/*, .env files, application secret stores
- Alert on the /proc/self/environ or .env read -> Secrets Manager call -> non-standard-port outbound chain, and on CloudTrail GetSecretValue region-fallback patterns
- Enable GuardDuty InstanceCredentialExfiltration.OutsideAWS findings
Affected and fixed versions
- marimo-team — marimo
Vulnerable versions: <= 0.20.4 (all versions prior to 0.23.0)
Fixed in: 0.23.0
Timeline of Marimo pre-auth shell
- GHSA-2679-6mx9-h9xc published disclosing unauthenticated /terminal/ws RCE in marimo <= 0.20.4 (reporter @q1uf3ng)
- Sysdig observed the first in-the-wild exploitation 9 hours 41 minutes after advisory publication; credential theft completed in under 3 minutes
- CVE-2026-39987 published in NVD (CVSS 3.1 9.8, CWE-306); fixed in marimo 0.23.0
- Start of the 2026-04-11 to 2026-04-14 window with 662 exploit events from 11 source IPs in 10 countries, including locked-ioc-13 deployment from locked-ioc-7
- End of the observed 662-event exploitation window
- CISA added CVE-2026-39987 to the Known Exploited Vulnerabilities catalog
- CISA remediation deadline for federal civilian agencies
- NVD record last modified
- tl;dr sec #346 highlights Sysdig's report of a hand-coded (non-LLM) operator going from WebSocket to SSH bastion login in 8 seconds
Sources cited for Marimo pre-auth shell
- GitHub Security Advisory GHSA-2679-6mx9-h9xc GitHub · Advisory · 2026-04-08
- NVD CVE-2026-39987 NVD (NIST) · Advisory · 2026-04-09
- CISA Known Exploited Vulnerabilities Catalog CISA · Government advisory · 2026-04-23
- Sysdig: Machine speed, hold the AI - Hand-rolled marimo CVE-2026-39987 exploit sysdig.com · Research
- Sysdig: Marimo OSS Python Notebook RCE from Disclosure to Exploitation in Under 10 Hours sysdig.com · Research · 2026-04-09
- tl;dr sec #346 tldrsec.com · News · 2026-09-17
- The Hacker News: Marimo RCE flaw CVE-2026-39987 The Hacker News · News
- Security Affairs: CVE-2026-39987 Marimo RCE exploited in hours after disclosure securityaffairs.com · News
- InfoWorld: Critical flaw in marimo Python notebook exploited within 10 hours of disclosure infoworld.com · News
Drafted by the Threadlinqs research pipeline from 9 sources; published ; last updated —.