Skip to navigation

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

criticalACTIVE

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

IdentifiersTL-2026-5360, CVE-2026-39987
Also known asGHSA-2679-6mx9-h9xc
SeverityCRITICAL
CVSS9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
First disclosed
Exploitation statusExploited in the wild; Listed in CISA KEV: CVE-2026-39987 (added )
EPSSCVE-2026-39987: 37.8% (98.5th percentile)
Affected productsmarimo-team marimo
Weaknesses (CWE)CWE-306
MalwareNKAbuse - S1107
ToolsNetcat, boto3
MITRE ATT&CK techniquesT1021.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
MotivationUNKNOWN
Target sectorstechnology, data science and analytics, cloud-hosted development
Target regionsGlobal
Detection rules9 (1 shown free)
Sources9 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_upgrades

Microsoft 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

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. Sign up free to unlock the detection rules or query them with the Threadlinqs MCP server (Purple)
  • 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.

15 indicators of compromise · unlock with TL Creds. Compare plans

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

T1021.004 SSH

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

T1571 Non-Standard Port

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

Drafted by the Threadlinqs research pipeline from 9 sources; published ; last updated —.

Threadlinqs Intelligence — Real-Time Threat Detection Platform

[ 0 threats ] [ 0 det ] [ CRIT: 0 ] [ HIGH: 0 ]
// threat_feed
$ sort --newest
Showing all threats

Live intelligence console

Threat level

Latest Threats