DEV Community

Cover image for 10 of 11 AI coding agents fell to the same shell trick
vladimirrotariu
vladimirrotariu

Posted on

10 of 11 AI coding agents fell to the same shell trick

Adversa AI disclosed GuardFall on 30 June 2026. Their bypasses got past the command filter in 10 of 11 open-source coding and computer-use agents. One held, Continue, and it held because it parses the command before judging it, rather than matching patterns in raw text.

The trick is decades old. The guard inspects the string the model produced, and bash rewrites that string before running it. A regex reads r''m and finds nothing to match, then bash strips the quotes and runs rm. $IFS expansion and command substitution do the same. Two parsers read the same bytes and disagree, and the second one runs as root.

Tightening the allowlist leaves that open, because the gap is the string itself.

Typed actions instead

I build SysKnife, a Linux sysadmin co-pilot that runs as an MCP server and accepts typed actions only. The tool surface carries no exec and no sudo-exec, so nothing on it takes text from the model and hands it to a shell. AptInstall { package: "ripgrep" } is a schema-checked value, and I compiled the daemon that carries it out against a fixed catalogue of 190 typed actions it cannot extend at runtime. An action outside that catalogue has no representation to reject.

Three properties follow from that.

The agent cannot approve its own work. sysknife_plan returns typed steps with risk levels and a transaction id, then stops. Execution needs a receipt that only sysknife approve <transaction-id> mints, typed by a human at a real terminal, valid once and for fifteen minutes. The daemon checks that receipt where the privilege sits, so an agent that talks itself into having permission gets refused anyway. There is no phrasing of "yes, go ahead" inside a chat window that substitutes for it.

Claude Code calling sysknife_plan, a human approving in a second terminal, and sysknife_execute consuming the one-time receipts

A deterministic reproduction of the Claude Code MCP flow, rendered offline by mcp-flow-mock.sh so it replays identically from a fresh checkout. Live LLM calls are nondeterministic and the tape renders with no daemon or provider configured, so the recording is scripted rather than captured. The same flow works in Cursor and Codex CLI.

Privilege stays behind a boundary the model cannot reach. The MCP server runs unprivileged. Every mutation travels to sysknife-daemon over a unix socket at mode 0660, inside a directory at 0750 sysknife:sysknife, and the daemon holds the sudoers and polkit policy. It reads the caller's identity from SO_PEERCRED, so the kernel names who connected and the request body never gets to claim it.

The daemon signs what it executes, and you can check the signature without the daemon. Rows join an Ed25519 hash chain that verifies against the exported public key alone, with no signing key and no access to the host that wrote the log. A row names the account that asked for it: uid:1000, or token:vsock when the caller presented a shared secret, or none:unattributed when the daemon could not establish an account. Signing an honest "I could not tell" beats signing a uid the kernel never vouched for.

Two commands

npx sysknife-setup --codex
sysknife audit verify --pubkey audit-key.pub
Enter fullscreen mode Exit fullscreen mode

The first appends an MCP block to ~/.codex/config.toml and writes an AGENTS.md, then downloads binaries it checks against a published SHA-256 sums file. The second is the one an auditor runs, and it needs nothing but the public key.

Codex is one of three targets. --claude merges .mcp.json and writes a rules file under .claude/, which is the setup the recording above shows. --cursor writes .cursor/mcp.json and its own rules file. --all does the three together, and the installer with no flag asks you which you want. The daemon and the approval boundary are identical underneath: the agent surface changes, the privilege boundary does not.

Support, stated by evidence

Ubuntu 20.04 and later are eligible, LTS and interim alike. Three of them, 22.04, 24.04 and 26.04, each passed the full story suite 79 of 79 on a live VM, each with a committed replay twin that reproduces the run offline. The support matrix lists the evidence release by release, so a release the suite has never run on says so. Fedora Atomic 41 and later is implemented and eligible, and waiting on a current VM run. MIT licence, and the code sends no telemetry.

One caveat about directory listings, including the ones that carry a green badge for this project. Boot the server inside a build container and all five MCP tools show up, because a tool list is static metadata and no privileged daemon exists in a sandbox. A green run there proves the binary starts and the schemas parse, and stops short of proving it administers a real host. For that part you have the VM runs, release by release, and a signed chain you can check for yourself.

Top comments (0)