Back to all posts
Published on · by Renaud Deraison

The guard and the shell disagreed

On June 30, Adversa AI disclosed GuardFall — a class of bypasses that walks decades-old bash tricks straight past the command guards built into 10 of 11 popular open-source coding agents. The reason is simple and unfixable at that layer: the guard reads the text, and bash rewrites the text before it runs. Bromure Agentic Coding never bet on the guard reading bash correctly. It bet on the command not mattering when it runs.

A coding agent's safety filter looked at the string r''m -rf ~ and saw a command it did not recognize, so it let it through. Bash looked at the same string, threw away the empty quotes, and ran rm -rf ~. The filter and the shell were reading two different languages. That gap is not a bug in one tool — it is the whole idea of guarding an agent by pattern-matching the commands it types, and Adversa AI just showed it fails in ten of the eleven agents they tested.

The command guard is the feature every agentic coding tool reaches for first. The agent proposes a shell command; a filter inspects the string; if it looks destructive — rm -rf, a piped curl … | sh, a dd to a raw device — the guard blocks it or asks you to confirm. It is the thing that lets you flip on auto-run and feel safe. On June 30, 2026, Adversa AI published research, which The Hacker News and SecurityWeek picked up, showing that the feature is theater.

What GuardFall is

They call it GuardFall, and it is not a CVE. There is nothing to patch, because it is not a flaw in one component — it is a mismatch between two of them. In Adversa's words: "A guard inspects raw text, while system shell (bash) expands, unquotes, and rewrites text before running it." The filter matches on the string the model emitted. Bash then runs that string through quote removal, variable expansion, and command substitution before a single byte executes. The two never look at the same command, so any trick that survives the rewrite but not the match sails through.

These tricks are older than most of the people using the agents. Adversa grouped them into five classes:

  • Quote removal. r''m is two tokens to a regex and the word rm to bash, which drops the empty quotes.
  • Variable expansion. rm$IFS-rf$IFS/ looks like one long word to a pattern matcher; bash expands the Internal Field Separator and gets rm -rf /.
  • Command substitution. $(echo rm) -rf / hides the dangerous verb inside $( ); the guard inspecting the outer string never sees rm.
  • Encoded pipelines. echo <base64> | base64 -d | sh — every visible token is harmless; the payload only exists after decoding.
  • Alternative destructive flags. No rm at all: find /x -delete, tar -C / -x, sed -i. A denylist built around rm and dd never fires.

Adversa ran these against eleven of the most-used open-source coding and computer-use agents — opencode, Goose, Cline, Roo-Code, Aider, Plandex, Open Interpreter, OpenHands, SWE-agent, and the Hermes project among them, carrying roughly 548,000 GitHub stars between them as of May. Ten fell. The one that held, Continue, held for a specific reason: instead of matching the raw string, it tokenizes the command the way bash will before deciding — absorbing the quotes, resolving $IFS, extracting the inner command from a substitution, flagging any pipe that ends in sh. It models the shell. That is the only thing that works at the guard layer, and it is real, careful engineering that the other ten will now have to reproduce, correctly, and keep correct with every bash edge case and every release.

AGENT EMITS ONE STRINGr''m -rf ~GUARD · matches raw textsees the tokensr''m · -rf · ~no rm found → allow ✓BASH · rewrites, then runsdrops the empty quotesrm -rf ~home directory gone
Why the guard loses. The agent emits one string; the pattern-matching guard reads it as written and finds nothing destructive; bash then expands, unquotes, and rewrites the same string into rm -rf before the filter's verdict means anything. The guard and the shell are looking at two different commands — the entire premise of GuardFall.

The layer is the problem, not the regex

The temptation is to read GuardFall as "ten teams wrote weak filters." That misses what Adversa is saying. Their sharpest sentence is not about any one agent:

An agent that can run arbitrary shell commands on the operator's host, gated by a regex matching the LLM's emitted string, is not a defense. It fails while fully enabled and correctly configured, because string matching cannot model what bash will run.

Fully enabled and correctly configured. This is not a misconfiguration you can close. The guard sits at the one layer where it can never win — between a model that writes strings and a shell that reinterprets them — and asks a text matcher to predict the output of a Turing-complete expander. Continue's tokenize-and-canonicalize approach narrows the gap by teaching the guard to think like bash, and it is the right move if you are going to keep a guard there. But it is a per-agent, per-release commitment to out-parse a shell that has spent thirty years accreting ways to rewrite a command. Nine other popular agents show how easy it is to get wrong.

Adversa's compensating controls are telling. Ahead of any structural fix, they tell you to redirect $HOME to a scoped sandbox that keeps project access but removes credentials, to strip auto-execution flags, to stop agents from running on untrusted pull requests, and to treat every repository config file as untrusted code. Read that list again. It is not advice about writing a better filter. It is advice about building a box around the agent so that when the filter loses — and they are telling you it will — the command that gets through lands somewhere it cannot hurt you.

That box is the product.

Bromure never asked the guard to read bash

Bromure Agentic Coding starts from the assumption GuardFall proves: the agent will, sooner or later, run a command you did not sanction. It does not try to catch that command by reading it. It runs the whole agent — Aider, Goose, opencode, whichever of the ten you like — inside a disposable Linux VM, and routes every network request out through a proxy on the host. The design question is never "will the filter recognize this rm?" It is "when the rm runs, what does it reach?"

Walk GuardFall's own payloads through that box.

The attack's point, once a hidden r''m or find /x -delete slips a booby-trapped README past the guard, is to steal what the account can reach — Adversa names ~/.ssh, ~/.aws, cloud credentials, "anything sitting in your home folder" — and to wipe or exfiltrate it. In Bromure, those are the things that were never in the box. The real secrets never enter the VM: when you give a profile an AWS_SECRET_ACCESS_KEY, a GitHub token, an Anthropic key, the agent's environment gets a brm_… placeholder, and the host proxy swaps the fake for the real value on the wire, only on the outbound request to the provider that should receive it. Adversa's recommended mitigation — move $HOME somewhere that "keeps project access but removes credentials" — is a hand-rolled, partial version of what a Bromure profile does by default, for every credential, without you scripting it.

WITHOUT — real home, real keysGuardFall payload runscat ~/.aws/credentials wJalrXUtn…cat ~/.ssh/id_ed25519 -----BEGIN…attacker gets working keysreal accounts, real blast radiusexfiltrateWITH BROMURE — disposable box, decoyssame payload runs (in VM)cat ~/.aws/credentials brm_d4e5f6…cat ~/.ssh/id_ed25519 brm_a1b2c3…HOST PROXY — real keys never enter the VM; swapped in only for the true provider requestattacker gets brm_… decoysauthenticate to nothinghome is a throwaway cloneexfil logged · reset-to-base erases it
GuardFall's payload gets through in both worlds — Bromure does not stop the command from running. Left: on a normal host, the injected command reads real ~/.ssh and ~/.aws material and ships it out; it works. Right: inside Bromure the same command runs, but the home directory is a throwaway clone, the credentials it finds are brm_… decoys the real keys live on the host, and the exfil attempt is a recorded line in the Security Log. The command executed and reached nothing.

The destructive half lands the same way. A find /x -delete or a sed -i that slips the guard does run — Bromure does not pretend it caught the command. It runs against a home directory cloned from a shared base, on a VM you can erase and reset to base from a menu, not an incident form. The persistence a payload might drop dies at that reset. And because every request leaves through the host proxy, the curl … | base64 -d beacon that GuardFall's encoded-pipeline class is built to hide is not invisible: it is a recorded, attributable line in the Security Log.

The one place the wire beats the shell

Bromure does keep a destructive-operation guard — its host-side Guardrails, which we wrote about after a Cursor agent deleted a production database in nine seconds. And here the layer matters in Bromure's favor, because Guardrails do not live where GuardFall lives.

GuardFall is a shell-text attack. Every one of its five classes — quote removal, $IFS, $( ), base64 pipelines, exotic flags — is a trick of how bash lexes a string. Bromure's Guardrails never read that string. They sit on the host proxy and classify the structured API call the agent makes to a provider it understands — the actual HTTPS request to AWS, Kubernetes, a git forge, a managed database — and return a hard 403 on the destructive ones, on the wire, where a compromised agent in the VM cannot switch them off. By the time a request reaches that layer it is already a parsed DeleteDBInstance, not a string of quotes and dollar signs. There is no shell rewrite to smuggle anything through, because there is no shell in the path. GuardFall's entire technique needs a bash between the check and the action; on the Guardrails path, there isn't one.

What this draws a line around, so we're clear

The command still runs

Bromure is isolation, not interception. A GuardFall payload that slips your agent's own guard will execute inside the VM. What Bromure changes is the blast radius: a throwaway home, decoy credentials, a logged egress path. If you need the command itself refused, that is your agent's guard's job — and GuardFall is why you shouldn't lean on it.

Local destruction is disposability, not a block

A find /x -delete against files inside the VM is not a provider API call, so Guardrails do not gate it. The answer to in-VM destruction is that the VM is disposable and the real work lives in a mounted repo and on the host, not that the delete was stopped. Reset-to-base, not rollback.

Substitution covers the credentials you configure

The brm_… swap protects the secrets you put in a profile — model keys, cloud and git tokens, managed database endpoints. A password you paste by hand into a file inside the box, or a token a script writes to disk mid-session, is just a file. Keep secrets in the broker, not in the workspace.

Egress is logged, not forbidden by default

The exfil beacon shows up in the Security Log; that is attribution, not prevention. Nothing real leaves because the credentials are decoys, but if you need the VM barred from talking to arbitrary hosts, that is a network policy you still set.

The part that generalizes

GuardFall is a clean statement of a rule the whole category keeps relearning: you cannot make an agent safe by asking a filter to predict what its command will do. The model writes a string, and somewhere below it a shell, an API client, a package manager, or a browser reinterprets that string with its own rules. Every defense that inspects the agent's output and hopes it matches the eventual behavior is betting against that gap, and the gap always wins — it won here in ten of eleven agents that were, in Adversa's words, fully enabled and correctly configured.

Bromure Agentic Coding does not take that bet. It assumes the command will run, the guard will sometimes miss, and the agent you invited in can be turned against you by a README you never wrote. So it makes the place the command runs not worth attacking: real keys on the host, decoys in the box, a disposable VM, a hard block reserved for the wire where no shell can rewrite it, and a log of everything that tried to leave. You can run any of the ten agents GuardFall broke — the outcome is the same throwaway box either way. It is free and open source.