Termaxa — a cooperative gate for the shell commands AI agents run

termaxa.com · 2026-09-29 · by Long Nguyen ·generic
The agent's prompt shows you the command; Termaxa shows you the consequence. It previews the blast radius, backs up first, blocks the dangerous ones, and keeps a record the agent cannot rewrite. Claude Code, Codex, Cursor, Copilot. Free and open source. Try it in your browser at play.termaxa.com.

Termaxa — a cooperative gate for the shell commands AI agents run

$ termaxa — a cooperative gate for the shell commands AI agents run

termaxa

Preview what will happen. · Protect what matters. · Roll back when you're wrong.

The agent's own prompt shows you the command it wants to run. Termaxa shows you the consequence. The files, the rows, the commit a force push would lose: previewed before anything runs, backed up first, the dangerous few blocked, and every decision in a record the agent cannot rewrite. Hooks for Claude Code, Codex, Cursor and Copilot. A windshield, not a sandbox.

This is the real gate, running on a throwaway project with the default policy. Nothing executes — `termaxa check` reads the command and answers. Try to get a destructive one past it.

rm -rf ./scratch git push --force origin main psql -c "DROP TABLE users CASCADE" echo TOKEN=abc123 > .env ls && echo ok && rm -rf ./scratch git -C . push -f origin main ls -la

check it

$ termaxa check "rm -rf ./scratch" press check it, or pick a chip

full playground, with what counts as beating it: play.termaxa.com

A real Claude Code 2.1.283 session with its own approvals switched off: it inspects scratch/ freely, tries rm -rf ./scratch, and Termaxa's hook stops it with the reason and the 12 files it would have taken; the agent declines to route around it

Inside the agent, not beside it: a real Claude Code session with its own approvals switched off. The inspection runs without a prompt; the `rm -rf` is stopped with the reason and the 12 files it would have taken, and the agent declines to route around it. Recorded on termaxa 0.19.5.

works with

claude code

hook on Bash and the write tools · live-tested · `wrap` reaches it too

codex

hook, deny only: an ask is a refusal there · apply\_patch read as writes

cursor

shell events and Write/Delete · the 3.11 hook API captured live

copilot cli

an ask arrives as a real prompt with the reason in it

herdr

launch an agent under the gate, tail the record, see why a pane went red `herdr plugin install termaxa/herdr-termaxa`

git

what a force push would destroy, pinned before it runs

postgres

rows and dependent tables, pg\_dump first, rollback after

terraform

the plan before the apply

shell

policy, delete previews, insurance, the record

install

brew cargo winget scoop binaries

`$ brew install termaxa/tap/termaxa`copy

macOS and Linux. Prebuilt, sha256-pinned; the formula's CI installs it on both before every release goes out.

linux x86\_64 macos arm64 macos x86\_64 windows

then, in a project: `termaxa init` writes the policy and the hook for whatever harness it finds · `termaxa doctor` proves it's wired · `termaxa replay` judges every command your agents have already run, so you know the ask ratio before you commit · `termaxa check "rm -rf /"` works anywhere, no setup

hardened by real use

33

releases

\~14,600

lines of Rust (+15,800 of tests)

532

regression tests

16

bugs found in the wild

4

live agents tested

⚠ shell segmentation bypass

A live agent rode a destructive command in behind a harmless prefix. Compound commands are now split and judged per-segment. (v0.7)

⚠ native tools bypass the shell

The same Cursor agent then used its built-in file tool, deleting files the shell hook never saw. For a year the answer was "non-shell tools need a sandbox". Since v0.19 the gate reads them: a `Write`, `Edit`, `Delete` or a patch's file headers is judged by the same path rules as a shell command, insured and receipted — and a file no rule names is silence, by design. Cursor's `Delete` passed through by default until a live capture on Sep 19. (v0.19–0.19.2)

⚠ an escaped quote walked past the gate

A backslash-escaped quote collapsed a compound command into one segment, so an anchored deny rule never saw the second command: `echo \" ; terraform destroy` matched `echo *` and was allowed. Live since v0.7.0. Found by unifying two shell parsers and reading what they disagreed about — the disagreements were the bug list. Advisory published. (GHSA-rv66-7qcx-c45j, v0.16)

⚠ the supervisor was never reached

Supervised mode passed 439 unit tests, 18 privilege assertions and three green CI platforms. Then a real agent ran `ls -la` as a real second user, and the gate decided locally: its hook looked for the supervisor in its own home and found nothing there. The walls held; the door led nowhere. Every automated test had run both halves as the same user. (v0.17)

⚠ Codex honoured none of our verdicts

The first live Codex session: the hook fired on every command, and Codex rejected every answer — an explicit allow, an ask, and a deny that exited 2 all failed the hook and fell open to Codex's own prompt. Codex honours exactly one PreToolUse verdict, `deny`, on stdout with exit 0. Fixed the same weekend; the captured payload is a regression test now, and a hard stop lands in Codex's own UI as "Blocked by hook" with the blast radius. (v0.18)

⚠ the wrapper never saw Claude Code

`termaxa wrap` shipped on the promise that a shell resolved by name reaches the gate. Then `strace` on a real session: Claude Code runs `/bin/bash` by absolute path when it finds no zsh, and every command walked past the shims with zero audit lines. It honours `CLAUDE_CODE_SHELL`, so `wrap` now points it at the shim. Codex runs its login shell by absolute path and offers no such lever; its hooks are the way in, and the docs say so. (v0.18.5)

\+ the other ten, with dates and versions →

⚠ git force-push blind spot

The preview said "nothing to push" while a force push destroyed a commit. Now it shows what the remote will lose. (v0.6.1)

⚠ repo-owned audit logs

Logs lived in the repo, so `git reset --hard` could erase them. State moved to `~/.termaxa`, out of git's reach. (v0.8)

⚠ retry-with-different-syntax

A live Cursor agent, blocked on a delete, switched shells (PowerShell → cmd) to sneak it past. Termaxa now classifies intent, not spelling, and trips a per-session circuit breaker on the repeat. (v0.11)

⚠ path syntax decided your backup

Two engines parsed the same command independently. `rm -rf C:\Users\x\Desktop` was insured; `rm -rf /c/Users/x/Desktop` — the same directory — silently wasn't. Found by building the delete preview and comparing the two. They now share one implementation. (v0.14)

⚠ the agent's API moved under us

Cursor 3.11 renamed its hook events — and Termaxa silently stopped gating it. Every test stayed green (fixtures used the old shape). Caught live via payload capture, fixed the same night; the real 3.11 payloads are regression tests now. (v0.11.4)

⚠ a delete with no verb in the list

Cursor's `Delete` tool arrived at the hook, matched no write verb, and fell through by default: `doomed.txt` was deleted with no line in the record. Found by a debug capture on Cursor 3.11.25 the day native writes went live. `delete` and `remove` are write verbs now, and the captured payload is the regression test. (v0.19.2)

⚠ every spelling but one

The hard stop read `git push*--force*`, so `git push -f` was an ask while `--force` was a deny. And `git -C /path push --force` matched no rule at all, hard stop included, because git's global options sit before the subcommand every rule was written for. Found in one live Herdr session where Claude Code spelled every git call with `-C`. Git's options are stepped over everywhere git is read; force is denied in every spelling. (v0.19.4)

⚠ the insurance followed the link

The preview counts a directory link as one entry and never walks into it. The backup copy branched on `is_dir()`, which follows links — so a folder holding a junction into a live tree passed the size cap as four files and then copied the whole tree behind it, uncapped, and a rollback would have put the link back as a real directory. That is the mechanism of a public incident that deleted 48,000 files through Windows junctions, inside our own insurance. A link is a link now. (v0.19.4)

⚠ a rule that could not read its own spellings

A genomics team published a security-reviewed deny list for the `gh` CLI with one line marked "genuinely open question, unverified". Answering it meant measuring both sides. Their side: Claude Code's matcher steps over an env prefix but lets `sh -c`, `eval` and `xargs` run. Ours: with a `gh repo delete*` deny, the plain form denied and five spellings the head resolver already understood came back as asks, because the string rules never saw the resolver's readings. Every spelling the resolver knows now feeds every rule; sudo is transparent to a deny and never to an allow. (v0.19.5)

⚠ the option beside the one a rule was written for

Tim Schipper built 0.19.5 from source and found five commands the starter allowed for reading that write: `git branch -M`, `find -fprint`, `git diff --output`, `sed -n -i` and inline `node -e`, each allowed with no prompt, no preview and no backup. Reproducing them turned up sed's `e` command, which runs any shell command under the same allow. And a push that deletes remote branches asked, but its preview said "nothing to push". Now the option that writes or runs turns the allow into an ask that names it, the file it would write is previewed and backed up first, and a push that removes refs says which. Published as GHSA-36jf-95xr-37f2, credited to him. (v0.19.6)

field notes

AUG 2026 Claude Code shipped a sandbox. Here's what it protects — and what it doesn't. Bash-only scope, reads wider than writes, a permissioned escape hatch, fail-open, no native Windows — and why containment isn't consequence isn't recovery. JUL 2026 I asked an AI agent to delete a folder my tool was guarding. Four rounds with a live Cursor agent: whack-a-mole retries, a classifier hole, a native-tool escape, and a silently renamed hook API.

what's next

\[x\] supervised mode — a daemon under your user decides; the agent runs as an account that cannot read the audit log, edit the backups, or stop it

\[x\] resolved-target rules — `> .env` and `> ./.env` are one file, not two strings

\[x\] hash-chained audit — an edited or removed entry is detectable

\[x\] the gate reads what the agent actually runs — `sh -c` strings, `bash -lc`, a redirect that isn't a target, a PowerShell assignment

\[x\] the closed side, by choice — `unrecognised: deny` and `backup_failure: deny` for unattended runs

\[x\] the wrapper reaches the agent — `wrap` steers Claude Code to its shims through `CLAUDE_CODE_SHELL`; Codex hardcodes its shell and is gated by its hooks instead

\[x\] native writes through the gate — `Write`/`Edit`/`Delete` and patch headers judged by path rules, insured, receipted

\[x\] `termaxa replay` — every command your agents ever ran, judged from their transcripts; the honest ask ratio before you install

\[x\] the playground — play.termaxa.com, the real gate in a browser, and the four published advisories as solved challenges

\[x\] herdr plugin — launch an agent under the gate, tail the record, see why a pane went red (`herdr plugin install termaxa/herdr-termaxa`)

\[x\] cursor — live-tested (incl. the 3.11 hook API)

\[x\] post-execution receipts — approved commands don't trip the breaker

\[x\] execution report — session & 30-day flight recorder

\[x\] termaxa doctor — is the gate actually wired up?

\[x\] delete blast radius — what an `rm` actually costs

\[x\] codex — live-tested on Windows; its hooks can only deny, so an ask is a refusal there

\[x\] copilot — live-tested; an ask arrives as a real prompt with the reason in it

\[ \] termaxa cloud (shared policies & approvals)

v0.19.6 today · built in the open · honest about what's not done

this tool is free forever.

open source under MIT / Apache-2.0 no feature gates. no license keys. ever. cloud sits on top of the free core, never in front of it.

from the maker

M

Manoj solo maker · builds developer infrastructure

I build with Claude Code every day, and I kept getting nervous handing it commands that touch git history and production databases. The built-in "allow this command?" prompt tells you what it wants to run — not what will actually happen.

So I built the thing I wanted: a gate that shows the real consequence, takes a backup before you approve, and lets you roll back. The first time I pointed a live agent at it, the agent chained a destructive command behind a harmless one and slipped past a naive rule. Watching that happen is why Termaxa now splits compound commands and judges each part — that exact bypass is a regression test today.

— Manoj · also the maker of ZeroDrop, email-verification infrastructure for developers

$ termaxa --version termaxa 0.19.6

github docs security [\[email protected\]](https://termaxa.com/cdn-cgi/l/email-protection#9ceff9ffe9eef5e8e5dce8f9eef1fde4fdb2fff3f1) changelog

also from the maker: zerodrop → email verification infrastructure for developers

termaxa.com · 1 images · 14 links

Comments (0)

No comments yet