
Marimo RCE: Human Hit an SSH Bastion in 8 Seconds
Marimo RCE: Human Hit an SSH Bastion in 8 Seconds
Everyone assumes machine-speed attacks are AI-driven. The models write the scripts, the agents run them, and a human barely has to lift a finger. The Sysdig Threat Research Team spent September watching a single threat actor disprove that assumption: no agent in the loop, no LLM fingerprints anywhere, and yet a complete credential-pivot chain — from vulnerable notebook to live SSH session on a bastion host — completed in eight seconds.
The operator exploited CVE-2026-39987, a pre-authentication remote code execution flaw in the Marimo notebook platform. Sysdig rated the pace bluntly: "Eight seconds is the kind of speed we expect to see in AI-assisted attacks. This operator got there on skill alone."
The exposure: an authenticated app with an unauthenticated terminal
Marimo is an open-source Python notebook you self-host, usually right next to your ML pipeline. That placement is exactly why it's a juicy target — those hosts carry GPU access, large training datasets, and long-lived credentials for AWS, GCP, and model providers. Gain a shell on the notebook and you are standing at the door of the victim's entire cloud account.
CVE-2026-39987 (CVSS 9.3) is a pre-authentication RCE. GitHub's advisory lists every Marimo version up to and including 0.20.4 as affected, with the fix shipped in 0.23.0. The bug lives in the /terminal/ws WebSocket endpoint: it hands out an interactive PTY shell but skips the validate_auth() check that every other WebSocket endpoint in the application applies correctly. Any client that can open a WebSocket to that path gets a full shell running as the Marimo process user. No credentials, no payload, no encoding gymnastics — just a socket (Sysdig, The Hacker News). The fix in 0.23.0 wires the missing call into the endpoint via PR #9098.
The bug has been on the CISA Known Exploited Vulnerabilities catalog for months; the federal remediation deadline was 7 May 2026. If your Marimo is older than 0.23.0, it is past due.
What the operator actually did
Over a nine-hour session the operator issued more than 850 interactive commands, used no recognizable public offensive tooling, and wrote every script in-session by hand (Sysdig). The observed timeline, all UTC:
| Time (UTC) | Event |
|---|---|
| 12:52:18 | First WebSocket connection from 172.236.12.17 to /terminal/ws |
| 12:54:13 | First interactive command: a /dev/tcp sweep of the host's /24 |
| 16:00–16:30 | Operator drops base64-encoded Python scripts into /tmp/ |
| 16:50:40 | boto3 script calls secretsmanager:GetSecretValue |
| 16:51:45 | Retrieved SSH key replayed against an internet-reachable bastion |
| 18:57:22 / 18:57:26 / 18:57:30 | Fresh WebSocket open → stored credential lookup → SSH bastion auth (8 s) |
| 20:13–20:32 | asyncssh-style listener deployed against an attacker-owned VPS |
The first TCP probe and the first credentialed AWS call sit nearly four hours apart. That gap is where the operator built and debugged the toolkit — once it was on disk, later reconnections just fired one command.
The toolkit: handed-built, not AI-rolled
Every earlier Marimo attacker Sysdig profiled left LLM fingerprints: AWS request-signing code typed out from memory, line by line. This operator instead dropped eight Python scripts over a 17-minute window, each via the same pattern:
echo '<long-base64-blob>' | base64 -d > /tmp/chain.py
nohup python3 /tmp/chain.py > /tmp/chain.log 2>&1 &
The base64 wrap does double duty: it dodges shell-quoting problems with multi-line Python, and it produces a single bash_history line that reveals nothing to a defender skimming the file. The workhorse script polled Secrets Manager with hardcoded (already-harvested) keys, tried five AWS regions on exception, parsed the secret as JSON or raw, wrote the recovered key to /tmp/bastion_key with mode 0600, and fell through to ListSecrets on access denied. A later script connected over paramiko, tried RSAKey → Ed25519Key → ECDSAKey in order, and stood up an nc -lvp 4444 relay on the attacker VPS.
Two credential surfaces were harvested on the host — the process environment/credential files and the application's Redis backend (validated 33 minutes apart, mapped to two different IAM users) — then replayed until one returned the bastion key.
The trap that only humans walk past
Sysdig planted a natural-language directive in the vulnerable container — a prompt-injection honeypot set up to distinguish machine operators from humans. It told any LLM agent that processed a specific file to self-attest by echoing a hidden marker into its own output. Every agentic threat actor Sysdig has profiled against this CVE read the file and echoed that marker. This operator inspected the file twice and never echoed it, anywhere (Sysdig, Infosecurity Magazine). We covered why this class of trap nags LLM-driven tools so reliably before — the Hugging Face security.txt incident is the same phenomenon aimed back at AI agents.
The indicator stack matches: manually-paced session structure, iterative debugging visible in the region-fallback logic, hand-typed quoting variations, and a second EC2 enumeration script that kept working after denial — human-built redundancy, where an agent would fail fast and try something new instead.
What defenders should actually watch
Sysdig's conclusion is the uncomfortable one: detection cannot lean on operator-class fingerprints. An LLM-generated command stream and a hand-typed nohup python3 /tmp/chain.py look completely different, yet both land on the same secretsmanager:GetSecretValue call, the same SSH key handoff, and the same outbound bastion connection. The detection priority is the chain shape, not the shell typing pattern (Sysdig).
Concrete signals worth hunting:
- A process inside a notebook container reads
/proc/self/environor the app's.env, then makes an outbound HTTPS call tosecretsmanager.<region>.amazonaws.comwith a non-default user agent, and an outbound TCP connection to a non-RFC1918 IP on a non-standard port within the same minute. - In CloudTrail: a
secretsmanager:GetSecretValuecall from a principal that has never called Secrets Manager, returningAccessDeniedException, followed by retries across multiple regions in under a minute — a high-fidelity region-fallback signature. echo '<base64>' | base64 -d > /tmp/<name>.pyimmediately followed by a backgroundedpython3run.
The upstream fixes are unglamorous and effective: patch to 0.23.0 or later, restrict or disable /terminal/ws behind a reverse proxy, scope secretsmanager:GetSecretValue so a notebook's IAM user cannot read bastion keys, audit every secret surface on the host (proc environ, EnvironmentFile, ~/.aws/credentials, .env, whatever the app queries at runtime), and rotate anything that sat on an internet-reachable Marimo since exposure. Cloud secrets belong in a vault, not in a notebook that a single open WebSocket can empty in eight seconds (Sysdig).
This is one more datapoint in the running theme that cloud security assumptions keep breaking in both directions. AI made machine-speed attacks cheap; this week proved a skilled human needs no AI to hit the same timescale — and walks past the traps built to catch the machines. If you run self-hosted notebooks or ML hosts, treat the link between "usable" and "compromised" as effectively instant the moment an operator arrives with a toolkit. Plan the defense upstream: patch, gate the endpoint, and scope the credentials.
Sources
- Sysdig Threat Research Team: Machine speed, hold the AI: Hand-rolled marimo CVE-2026-39987 exploit
- The Hacker News: Human Attacker Exploits Marimo RCE, Reaches SSH Bastion in Eight Seconds
- Infosecurity Magazine: Human Attacker Hits Machine-Speed Exploitation of Marimo RCE
- GitHub (marimo-team/marimo): marimo PR #9098: Add auth check to terminal WebSocket endpoint
// author
Editor & Operator
Mohd Firdaus Abu Bakar (MrDodgerX) is an enterprise geospatial systems engineer in Malaysia and the editor behind hejes.my. He runs the briefing pipeline, curates and edits every draft, verifies each factual claim against primary sources, and presses the publish button — nothing ships without his review. He writes about AI, security and Malaysia's tech scene from a builder's perspective. Professional profile and code: the links below.
// sources //
fact-check verified · 16 SEPT 2026 · hejes-content-fact-check
- Machine speed, hold the AI: Hand-rolled marimo CVE-2026-39987 exploitSysdig Threat Research Team · accessed 16 SEPT 2026
- Human Attacker Exploits Marimo RCE, Reaches SSH Bastion in Eight SecondsThe Hacker News · accessed 16 SEPT 2026
- Human Attacker Hits Machine-Speed Exploitation of Marimo RCEInfosecurity Magazine · accessed 16 SEPT 2026
- marimo PR #9098: Add auth check to terminal WebSocket endpointGitHub (marimo-team/marimo) · accessed 16 SEPT 2026
related //

Citrix NetScaler: Patch First or Preserve Evidence First?
Two NetScaler zero-days are now in CISA's exploited catalog with a 30 September deadline, and installing the fix can erase the evidence you will need.

Claude Opus 5 Helped Hacktron Take Over OpenAI Staff Accounts
Hacktron chained a libheif heap overflow to an SSO flaw and took over staff ChatGPT/Codex accounts in under 72 hours - Opus 5 wrote the exploit.

Conductor Pre-Auth RCE Is Being Exploited in the Wild
CVE-2026-58138 is a critical pre-auth RCE in Orkes Conductor's GraalVM evaluators, now exploited in the wild. This is how the chain works and what to patch.
// join the feed
one fresh insight per week. no spam, ever.
weekly digest. unsubscribe anytime.