
Autopsy 10 · Post-compromise lateral movement | Comic Strip 11 · Four Pivots
Would Ethosure have caught this? Sysdig’s LLM-driven post-exploitation in four pivots
An attacker exploited a Marimo notebook, then handed the reins to an LLM agent that pivoted four times — from notebook to cloud credentials to AWS Secrets Manager to an internal PostgreSQL database — in under one hour. The bastion phase alone ran in under two minutes. Signature-based detection tuned for scripted attackers is the wrong instrument for an agentic adversary.
The incident
Marimo pre-authentication remote code execution was disclosed on April 8, 2026 as CVE-2026-39987 at CVSS 9.3. The vulnerable /terminal/ws WebSocket endpoint provided an interactive PTY-backed shell but omitted the validate_auth() call used by the application’s other WebSocket endpoints. First observed exploitation in the wild occurred nine hours and forty-one minutes after the advisory was published — before any public proof-of-concept existed. On May 10, 2026 Sysdig’s Threat Research Team captured a full intrusion. From an origin at 157.66.54.26 (AS141892, Indonesia), an attacker compromised an internet-reachable Marimo host and then let an LLM agent execute the post-compromise sequence: harvest two cloud credentials, replay them through a Cloudflare Workers egress pool, retrieve an SSH private key from AWS Secrets Manager, and use eight parallel SSH sessions against an internal bastion to dump a full PostgreSQL database. End-to-end: under one hour. Bastion phase: under two minutes.
- Sysdig — AI agent at the wheel: how an attacker used LLMs to move from a CVE to an internal database in 4 pivots
- Ibid. — Cloud Security Alliance Labs — LLM Agents as Active Post-Exploitation Tools (research note PDF)
- Ibid. — Breached.company — First documented in-the-wild attack: LLM agent autonomously pivots from Marimo RCE to internal database in four steps
The applicable coding-ethos policy
detection-tempo-parityCoding-ethos policies are compiled from principle-owned CEL expressions. Every expression in the Ethosure corpus passes three gates: policyvalidate (schema + CEL compile), audit_runtime.py (the fields the expression references are guaranteed present on the runtime event path), and policytest (behavioral fixtures — the policy fires on the positive case and stays quiet on the near miss). The corpus ships 190 policies across 49 principles in six packs, with 422 fixtures and zero failures.
Walkthrough
- The novelty is not the vulnerability. Marimo’s missing auth check is a classic bug and the CISA KEV listing is proof. The novelty is the tempo. Four pivots in under an hour. A full database dumped in under two minutes. Any detection tuned to a script-kiddie cadence loses that race by design.
runtime.behaviour.bulk_export_rate_gatefires on any egress event whose payload size or row count exceeds a per-workload threshold in a rolling window measured in seconds rather than minutes. It returns severityblock. The Sysdig bastion phase ran in under 120 seconds; a minute-scale window would never have caught it. The policy makes the window seconds-scale and pushes the threshold down to a value the workload owner has to approve.secrets.retrieval.workload_binding_requiredtargets the AWS Secrets Manager step. A notebook host has no legitimate reason to retrieve an SSH bastion key. The expression fires on any Secrets ManagerGetSecretValuecall whose caller workload does not match the secret’s declared consumer list. Attach the consumer list at secret creation time and the pivot dies at the credential boundary.runtime.identity.marimo_no_outbound_sshis the third expression. It is deliberately narrow. Marimo runtimes exist to serve reactive notebooks; they do not originate SSH sessions. The expression fires on any process in a Marimo-labelled runtime whose network profile includes an outbound port-22 connection. Eight parallel SSH sessions were never legitimate.
Policy YAML
# Pack: regulated-enterprise-base (v2)
# Overlay: model-provider-governance
principles:
- id: detection-tempo-parity
title: Detection Tempo Parity
directive: >-
Detection windows and thresholds must be sized for agentic adversaries operating
at second-scale, not for scripted attackers operating at minute-scale.
policy:
expressions:
- id: runtime.behaviour.bulk_export_rate_gate
scope: agent_action
severity: block
event: PreToolUse
principle_ids:
- detection-tempo-parity
- security-by-design
skill_id: post-compromise-hardening
when: >
tool_call.category in ["data_export", "http_request", "db_query"]
&& export.window_seconds <= 120
&& (export.rows > workload.export_rate_threshold.rows
|| export.bytes > workload.export_rate_threshold.bytes)
message: >-
Export rate exceeds the workload's approved second-scale threshold.
advice: >-
Reject the export. Raise the threshold only through a signed change entry recorded
in the evidence layer. The Sysdig bastion phase moved an entire database in under
two minutes; the detection window must be smaller than the attack window.
- id: secrets.retrieval.workload_binding_required
scope: agent_action
severity: block
event: PreToolUse
principle_ids:
- detection-tempo-parity
- least-authority-in-the-pipeline
when: >
tool_call.category == "secrets_manager_get"
&& !secret.declared_consumers.exists(c,
c.workload_id == runtime.workload_id)
message: >-
Secrets Manager retrieval from a workload that is not on the secret's declared
consumer list.
advice: >-
Add the workload to the consumer list with a signed change entry, or reject the
call. Notebook hosts should never appear on a bastion-key consumer list.
- id: runtime.identity.marimo_no_outbound_ssh
scope: agent_action
severity: block
event: PreToolUse
principle_ids:
- detection-tempo-parity
- security-by-design
when: >
runtime.labels.exists(l, l == "marimo")
&& tool_call.category == "network_connect"
&& tool_call.destination.port == 22
message: >-
Marimo runtime is attempting an outbound SSH connection.
advice: >-
Reject the connection. If the workload genuinely requires SSH egress, remove the
marimo label and re-declare the runtime kind; the label carries the assumption that
SSH is not part of the workload's normal behaviour.
Assurance boundary. Fixtures prove that synthetic positive cases fire and near-misses stay quiet. They do not establish production alert rates. DLP-backed controls depend on upstream detection, shell-command regexes are defense in depth rather than a sandbox boundary, and regulatory tags are engineering aids requiring counsel review.
Where this policy lives in the corpus
The Ethosure importable corpus ships six coding-ethos packs. Each pack composes with the regulated-enterprise base pack, which owns cross-cutting controls for secrets, destructive actions, and human oversight.
- Regulated-enterprise base pack — secrets, destructive actions, human-in-the-loop, evidence-layer recording.
- Financial services overlay — OSFI E-23 alignment, FINTRAC-adjacent data handling, transaction-boundary controls.
- Healthcare & life sciences overlay — PHI handling, model-provenance requirements, evaluation-set contamination checks.
- Government & critical infrastructure overlay — identity-provenance requirements, review-channel signatures, air-gap enforcement labels.
- Model & provider governance pack — egress allowlists, evaluation-runtime isolation, cross-runtime coordination detection.
- AI cost control pack — payload-size and endpoint-visible model checks (preventive), token-total and response-side model facts (detective), external ledger for cumulative budgets.
Published September 3, 2026 · Ethosure · Part of the Autopsy Series.