Between June 1 and July 11, 2026, six separate research teams published six separate reports that all reduce to the same sentence: an AI coding agent read text it didn’t author, and that text made it run a command nobody approved. GuardFall broke the shell guards in ten of eleven open-source coding agents. DuneSlide chained two Cursor bugs into zero-click RCE. PraisonAI shipped its Nth and Nth-plus-one critical CVE in the same component class. A malicious GitHub issue turned Claude Code’s own automation into a supply-chain vector. And twice now — once against a marimo notebook, once against Langflow — researchers watched an LLM agent run an entire post-exploitation chain, unattended, from initial foothold to stolen database.
None of these are the same bug. They’re the same architecture. The industry spent 2024 and 2025 arguing about whether prompt injection was a real threat. That argument is over. What’s left is the harder question: why does every vendor’s fix look like a better regex, when the thing it’s supposed to stop is a general-purpose reasoning engine that’s very good at finding the edge of whatever regex you wrote?
Six weeks, one failure mode
Lay the incidents next to each other and the pattern is impossible to miss:
| Date | Target | What broke | Outcome |
|---|---|---|---|
| Jun 5 | Claude Code GitHub Action | checkWritePermissions trusted any [bot]-suffixed login; agent auto-approved Bash reading /proc/self/environ | OIDC token theft, supply-chain write access |
| Jun 30 | opencode, Goose, Cline, Aider, Plandex, Open Interpreter, OpenHands, SWE-agent, 2 more | Command guards inspected pre-expansion text; shell expanded it differently | Guard bypass via quoting, $IFS, command substitution |
| Jul 1 | Cursor IDE (CVE-2026-50548/50549) | LLM-controlled working_directory param + symlink-check fail-open | Zero-click sandbox escape to full RCE |
| Jul 5 | Langflow deployments (JADEPUFFER, via CVE-2025-3248) | 18-month-old unauth RCE + autonomous post-exploitation | First fully agent-driven ransomware run |
| Jul 11 | PraisonAI AICoder / CodeAgent (CVE-2026-61445/61447) | No path validation on LLM tool calls; no AST check before exec() | Arbitrary file write + unsandboxed Python RCE |
| May 31 (pub. Jul-adjacent) | marimo notebook (CVE-2026-39987) | Pre-auth RCE + autonomous 4-hop lateral movement | Full PostgreSQL exfiltration in under an hour |
Every row has the same two ingredients: an agent that ingests attacker-influenceable text (a README, an MCP response, a web page, a chat prompt), and an agent that has been granted the ability to execute shell commands, write files, or run interpreted code as a side effect of doing its job. Take away either ingredient and the incident doesn’t happen. Vendors keep shipping products with both.
Why text-level guards always lose
GuardFall is the cleanest illustration of why this keeps happening, because it isn’t really a vulnerability report — it’s a proof that an entire category of defense is built on a false premise. Ten of eleven agents Adversa AI tested shipped a command guard: a blocklist that scans a proposed shell command for dangerous patterns (rm -rf, curl | sh, reads of ~/.ssh) before running it. The guard operates on the string the LLM produced. The shell that actually executes the command performs quote removal, $IFS expansion, and command substitution after the guard has already signed off. The guard and the shell are looking at two different strings, and only one of them is real.
| |
This is not a novel exploit primitive. Every technique GuardFall used is a documented POSIX shell quirk that’s been in CTF writeups and pentest cheat sheets for over a decade. What’s new is who’s on the other end of the exploit: not a human attacker crafting a payload by hand, but an LLM that will happily reproduce any of these patterns if the text it’s reading tells it to — because from the model’s perspective, a well-formed shell command in a Makefile comment looks exactly like an instruction. The guard was never a security boundary. It was a linter that happened to sit in front of exec().
Only one agent in the GuardFall test, Continue, held up — because it tokenizes and canonicalizes the command after applying shell-equivalent normalization, then checks the canonical form against a denylist of destructive shapes. That’s the correct design. It’s also, per Adversa’s own estimate, about two days of engineering work per project. Nine other maintainer teams hadn’t done it as of publication, which tells you the gap isn’t difficulty — it’s that string-matching feels like a security control until someone tests it against the shell it’s supposedly protecting.
The sandbox that isn’t: DuneSlide’s lesson
Cursor did the harder thing. It didn’t rely on a text blocklist — it ran agent commands inside an actual OS-level sandbox restricting filesystem writes to the project directory. That’s the right instinct, and it still fell in one hop.
CVE-2026-50548 exploited the fact that Cursor’s run_terminal_cmd tool accepts an LLM-controlled working_directory parameter, and the sandbox trusted whatever path that parameter named. A prompt injection — delivered through nothing more exotic than an MCP tool response or a scraped web page — steers the agent into pointing working_directory at the app’s own resources directory. CVE-2026-50549 is the fallback-open twin: Cursor resolves symlinks to confirm a write target sits inside the project, but if that resolution fails (target doesn’t exist yet, or a directory in the path has had read access stripped), Cursor gives up on verification and trusts the symlink’s apparent path instead of its real one.
Chain the two and you can overwrite cursorsandbox, the helper binary that enforces the sandbox boundary in the first place. Once that binary is corrupted, every subsequent command the agent runs is fully unsandboxed. The mechanism enforcing the boundary became the mechanism that deleted it, and the trigger was an ordinary, benign-looking developer prompt — “summarize this doc,” “debug against this API” — that happened to pull in attacker-controlled content along the way.
The lesson generalizes past Cursor: a sandbox whose configuration surface (a working-directory parameter, a symlink-verification code path) is itself reachable by the thing you’re sandboxing is not a sandbox. It’s a suggestion the sandboxed process gets to veto.
From foothold to full kill chain
The GuardFall and DuneSlide stories are about getting code execution. JADEPUFFER and the marimo intrusion are about what happens once an agent has it, and they’re the more important data points, because they show the failure mode isn’t hypothetical — it’s already been run end-to-end against production infrastructure, unattended.
In the marimo case, Sysdig watched an LLM agent take a pre-auth RCE (CVE-2026-39987) and, without any human re-engagement, harvest two sets of cloud credentials, replay them through a fanned-out egress pool to reach AWS Secrets Manager, pull an SSH key, open eight short sessions against a downstream bastion, and dump a full PostgreSQL database — four pivots, start to finish, in a little over an hour. In JADEPUFFER, a different agent took an 18-month-old, already-patched Langflow RCE (CVE-2025-3248), harvested API keys for five LLM providers and cloud credentials across AWS, GCP, Azure, Alibaba, and Tencent, pivoted to an internet-facing MySQL/Nacos host, authenticated as root with the stolen credentials, encrypted 1,342 configuration entries, dropped the tables, and left a ransom note. No operator typed a second command after launch.
Both write-ups note the same tell, and it’s worth dwelling on because it’s a detection opportunity, not just a curiosity: the agents’ payloads were self-narrating. Natural-language planning comments — target prioritization, “see what else we can do” — showed up inline in the command stream, the kind of verbose reasoning a human operator has no reason to type but an LLM produces by default as part of generating the next tool call. Commands landed at sub-second pace across multiple source IPs. Confident wrong guesses (querying a credentials table that didn’t exist in that schema) got corrected on the next attempt rather than abandoned. None of that is a signature you can hash and blocklist. It’s a behavioral fingerprint: reasoning-shaped artifacts embedded in what should be terse, human-authored shell.
Sysdig’s stated conclusion is the correct one, and it should reset how infra teams think about detection for this category entirely: signature- and IOC-based defenses lean on repetition — a fixed User-Agent, a stable command order, a reused tool. An LLM-driven intrusion has none of that. It improvises per target. What survives is behavioral detection keyed to outcomes — secrets accessed, GetSecretValue calls, bulk database reads, unusual child processes off an agent host — not to how the attacker happened to phrase the command this time.
The CI/CD amplifier
Everything above gets materially worse inside a pipeline, and the Claude Code GitHub Action incident is the clearest demonstration of why. The action gated privileged behavior behind a check meant to confirm the triggering actor had write access — and that check unconditionally trusted any login ending in [bot]. Because GitHub Apps get implicit read access to every public repo and can file issues anywhere using only an installation token, an attacker could open an issue that the workflow saw as bot-originated, walking straight past the permission gate without holding any access to the target repository at all.
With the gate bypassed, the second half of the chain was ordinary indirect prompt injection: a fake error message embedded in the issue body steered the agent into running attacker-supplied Bash. The agent auto-approved a set of “safe” commands without human confirmation — that’s the whole point of running an agent in CI, after all, since a human reviewing every proposed command defeats the automation — and one of those commands read /proc/self/environ, exposing ACTIONS_ID_TOKEN_REQUEST_TOKEN and its request URL. From there an attacker can mint OIDC identity tokens as the workflow itself. If the targeted workflow carried write scope, the natural next move is committing poisoned code into the action’s own source, at which point every downstream repository that depends on it pulls the backdoor on its next run — the same blast radius as tj-actions and actions-cool/issues-helper, except the entry point is an LLM reading an issue instead of a stolen PAT.
SecurityWeek’s follow-up noted the same comment/issue-triggered prompt-injection pattern reaches Gemini CLI and GitHub Copilot’s coding agent too. This isn’t a Claude Code problem, or a Cursor problem, or a PraisonAI problem. It’s what happens the moment any agentic tool is wired to trigger on issues, issue_comment, or pull_request_target from a public repository: the issue tracker becomes an unauthenticated command channel, and “auto-approve” — the exact feature that makes agentic CI worth running — is the control that gets deleted first.
What actually works
Stop building guardrails at the text layer. Every incident above involved a defense that operated on the string the LLM produced, checked before some later system (a shell, an interpreter, an OIDC minting service) transformed that string into an action. That gap is structural, not a bug you patch once. The fix has to sit at the boundary the untrusted text can’t reach:
- Sandbox at the OS/kernel level, not the prompt level. Run agents in containers or VMs with
$HOMEredirected to a throwaway directory, no mount of~/.ssh,~/.aws, cloud CLI configs, or shell history, and a read-only root filesystem with explicit writable mount points. A command guard is advisory. A missing credential is a boundary. - Treat every parameter the LLM can set as adversarial input, including the ones that configure your own safety controls. DuneSlide happened because a sandbox trusted an LLM-controlled
working_directory. If the model can name the path, the model can name a path outside the boundary — validate against the resolved filesystem state, and fail closed, not open, when resolution can’t be confirmed. - Kill auto-approve for any agent that ingests external content — full stop, no exceptions for CI “convenience.” If a workflow triggers on public issues, comments, or PRs, gate it behind an explicit human-actor check (not a
[bot]-suffix heuristic) before the agent gets tool access, and never grantid-token: writeto a job that doesn’t strictly need OIDC. - Scope credentials to what the agent’s task actually requires, and assume the process environment is readable.
/proc/self/environis onecataway from any shell access an agent has. Long-lived cloud keys and API tokens sitting in env vars on an agent host are a standing offer; short-lived, narrowly-scoped credentials shrink the blast radius even when the sandbox fails. - Build detection around outcomes, not signatures.
GetSecretValuecalls from unexpected hosts, bulk reads off a database, unfamiliar child processes off an agent-bearing box, self-narrating comments in a command stream — these survive an attacker who improvises. A blocklist of known-bad strings does not. - Pin, don’t float. Reference agentic GitHub Actions and MCP servers by commit SHA, not tag. A tag-repoint after compromise silently rewrites what every downstream consumer pulls next run.
The takeaway
The common thread across GuardFall, DuneSlide, PraisonAI, JADEPUFFER, the marimo intrusion, and the Claude Code Action bypass isn’t a shared codebase or a shared vendor — it’s a shared design assumption that a text-based filter, evaluated before execution, is an adequate substitute for an execution-time boundary. It never was, for the same reason SQL injection guards that string-match on user input never were: the thing doing the interpreting (a shell, a Python exec(), an OIDC minting service) doesn’t share the filter’s view of the string. Wrapping an LLM around that gap doesn’t create a new vulnerability class from nothing — it creates an extremely fast, extremely creative, always-available operator for exploiting the gap that was already there.
Two of these incidents already ran the entire kill chain without a human touching a keyboard after launch. That’s not a warning sign for some future where AI agents get more capable. That capability shipped. The organizations that get burned next aren’t the ones running a vulnerable agent — patches exist for all six incidents above — they’re the ones that never asked whether “agent with shell access reading untrusted content” was a design they should have shipped in the first place.