A Human Operator Matches Machine-Speed Exploitation
The Sysdig Threat Research Team (TRT) has published a forensic breakdown of a real intrusion that answers a question defenders have been asking all year: once AI-assisted attackers can chain exploitation and lateral movement in seconds, can a skilled human still keep up without help? In this case, the answer was yes. Sysdig traced a single operator moving from an unauthenticated WebSocket shell on a vulnerable Marimo notebook server to authenticated SSH access on an internal bastion host in eight seconds — using a hand-written Python toolkit, with no AI assistance detected anywhere in the chain.
The underlying flaw is CVE-2026-39987, the critical pre-authentication remote code execution vulnerability in Marimo that CosmicBytez Labs previously covered as a standalone advisory after it landed on CISA's KEV catalog. This piece focuses on what Sysdig observed after that initial foothold — the attacker's tradecraft, tooling, and pace — because it reframes how defenders should think about detection-and-response speed in the post-AI-attacker era.
Incident Summary
| Field | Details |
|---|---|
| Vulnerability | CVE-2026-39987 — unauthenticated PTY shell via Marimo's /terminal/ws WebSocket endpoint |
| Affected Versions | Marimo up to and including 0.20.4; fixed in 0.23.0 |
| Researcher | Sysdig Threat Research Team (TRT) |
| Session Length | ~9 hours (12:52 – 21:50 UTC), 850+ interactive commands |
| Time-to-Bastion (staged run) | 8 seconds — fresh WebSocket connection to SSH bastion authentication |
| Tooling | 5 hand-written Python scripts, staged via base64 to /tmp/; boto3, Paramiko, an AsyncSSH-style listener |
| AI Involvement | None detected — no LLM fingerprints, no public offensive tooling |
| Attacker Infrastructure | Source 172.236.12.17 (London); callback 45.79.187.72 (Cedar Knolls, NJ) — both on AS63949 (Akamai Connected Cloud / Linode) |
What Happened: Two Sessions, One Chain
Sysdig's telemetry shows the intrusion wasn't a single lucky burst of speed — it was the payoff of methodical preparation. The operator worked in (at least) two distinct sessions against the same compromised Marimo instance:
- Session one (starting 12:52:18 UTC) was reconnaissance and toolkit-building: scanning the internal subnet, writing and testing Python scripts directly on the compromised host, and validating that a harvested AWS key could reach the target bastion.
- Session two (starting 18:54:31 UTC) reused what session one had built. By the time the operator opened a third, fresh WebSocket connection at 18:57:22 UTC, the entire credential-theft-to-bastion-login chain fired from a single backgrounded command — completing in eight seconds because every dependency was already staged on disk.
Attack Timeline (UTC)
| Time | Event |
|---|---|
| ~28 hrs prior | First harvested AWS credential validated (visible later in CloudTrail) |
| 12:52:18 | Initial WebSocket connection to /terminal/ws from 172.236.12.17 |
| 12:54:13 | First interactive command — TCP sweep of the internal RFC1918 subnet |
| 16:00 – 16:30 | Five base64-encoded Python scripts written to /tmp/ |
| 16:50:40 | Script calls AWS Secrets Manager GetSecretValue via boto3 |
| 16:51:45 | Retrieved SSH key tested against the bastion host |
| 18:54:31 | New WebSocket session opens |
| 18:54:45 | AWS Secrets Manager call fires 14 seconds after session open |
| 18:56:32 – 18:56:50 | EC2 enumeration attempts (all denied by IAM) |
| 18:57:22 | Fresh WebSocket session opens |
| 18:57:30 | SSH bastion authentication observed — 8 seconds later |
| 20:13 – 20:32 | AsyncSSH-style listener deployed for follow-on access |
| 21:50:14 | Operator disconnects |
The vulnerability itself made this possible: Marimo's /terminal/ws WebSocket endpoint exposed a fully interactive PTY shell but, unlike other WebSocket routes in the same application, skipped the validate_auth() call that would have required credentials. Any client that opened a connection got a shell as the Marimo process user — no login, no token, no exploit chain beyond "connect." Marimo shipped the fix in version 0.23.0.
The Toolkit: Five Hand-Written Scripts, Staged for Speed
Sysdig recovered five Python scripts the attacker wrote and refined over several hours, each deployed with echo '<base64-blob>' | base64 -d > /tmp/<name>.py — a technique that sidesteps shell-quoting problems and keeps the actual script contents out of plaintext bash_history.
- Script 1 — Initial Secrets Manager retrieval, with region fallback across
us-east-1,us-west-2,eu-west-1,ap-southeast-1, andus-east-2. - Script 2 — A refined retrieval routine adding key persistence (
chmod 0600), JSON parsing, and a fallback toListSecretsif direct access was denied. - Script 3 — A standard-library-only reverse shell built from
socket,os.dup2, andsubprocess, with error handling and a 10-second timeout. - Script 4 — SSH connection logic via Paramiko, with detection for
RSAKey,Ed25519Key, andECDSAKey, plus anetcatlistener on port 4444. - Script 5 — A consolidated "chain" module tying the previous four together into the single command that ran in 8 seconds (Sysdig only partially recovered its contents).
The operator also probed an EC2 Instance Connect fallback — attempting to push an SSH public key using a placeholder instance ID (i-0000000000000000) after enumeration attempts were denied. AWS blocked it. Sysdig also found evidence the attacker pursued two separate credential paths in parallel: one harvested from environment variables/files on the host, and a second pulled from the application's own Redis backend, each tested against different IAM identities.
No AI Fingerprints — and a Trap That Didn't Work
What makes this report notable isn't the exploit itself; it's what Sysdig didn't find. There was no LLM-generated code, no agentic tool-calling pattern, and no publicly available offensive framework anywhere in the session — just four-plus hours of manual scripting, testing, and debugging by a person who evidently knew AWS IAM and SSH key handling well. Sysdig had also planted a prompt-injection probe inside the compromised Marimo container as a tripwire for AI-driven operators; the human attacker didn't take the bait, which the researchers noted is exactly the kind of judgment call that trips up agentic attack tooling.
Sysdig's own framing of the finding:
"Eight seconds is the kind of speed we expect to see in AI-assisted attacks. This operator got there on skill alone."
The team went further, arguing that the two attacker profiles — human and AI-driven — don't fail the same way, which matters directly for detection engineering:
"A skilled human will outmaneuver an LLM-driven operator on evasion and judgement alone... defenses built for one will likely miss the actions of the other."
Why This Matters for Defenders
The headline number — eight seconds — is a useful shock value, but the real takeaway is about the assumptions built into a lot of current detection logic. Many "time to detect / time to respond" targets, and a good chunk of anomaly-based tooling, were tuned around the idea that fast correlates with automated, and that automated activity looks different from a human at a keyboard (typos, pauses, trial-and-error). This case shows a human operator who eliminated all of that by doing the trial-and-error offline, in an earlier session, then executing the finished chain as a single pre-staged command. The activity that actually hit the bastion host looked instantaneous and clean — nothing about its speed alone would flag it as either "bot" or "human."
Detection & Defensive Recommendations
Indicators of Compromise
- Source IP
172.236.12.17; callback/C2 IP45.79.187.72 - File artifacts:
/tmp/chain.py,/tmp/full_chain.py,/tmp/bastion_key(mode 0600),/tmp/chain.log,/tmp/chain_output.txt,/tmp/callback.log - Behavioral markers: a base64-decode-then-execute pattern feeding straight into
python3;boto3region-fallback iteration; ParamikoSSHClientconnections originating from non-SSH binaries; an early, out-of-placepip3 install boto3
Cloud and runtime detection
- In CloudTrail, alert on
secretsmanager:GetSecretValuecalls from newly-seen principals followed byAccessDeniedExceptionretries across multiple regions within about 60 seconds — the region-fallback pattern is a strong signal on its own. - In GuardDuty, watch for
UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWSfindings tied to notebook or data-science compute. - Apply container runtime policies that flag drift and unexpected outbound egress from notebook/Jupyter-style containers.
- Restrict egress from notebook services to only the endpoints they legitimately need (package registries, model APIs) — a bastion-bound SSH connection or an unfamiliar HTTPS callback from a notebook container should stand out immediately.
Remediation priority
- Upgrade Marimo to version 0.23.0 or later on every instance — this flaw is already confirmed under active exploitation and sits on CISA's KEV catalog.
- Put the
/terminal/wsendpoint behind authentication, or disable the terminal feature entirely if it isn't required. - Audit every credential-storage surface reachable from a notebook host: environment variables, systemd unit files,
~/.aws,.envfiles, and any connected data backend (Redis, in this case). - Scope Secrets Manager and IAM permissions available to notebook hosts down to least privilege — dual-path credential harvesting only works if both paths lead somewhere useful.
- Rotate any credentials that were reachable from an internet-exposed Marimo instance, treating them as compromised regardless of whether exploitation is confirmed.
- Hunt specifically for staged toolkits: unexplained
/tmp/*.pyfiles, base64-decoded script artifacts, and SSH private key material written outside expected paths.
References
- The Hacker News — Human Attacker Exploits Marimo RCE, Reaches SSH Bastion in Eight Seconds
- Sysdig — Machine Speed, Hold the AI: Hand-Rolled Marimo CVE-2026-39987 Exploit