DEV Community

Cover image for I built Aegis — a firewall between AI coding agents and your shell
Ilias Almerekov
Ilias Almerekov

Posted on

I built Aegis — a firewall between AI coding agents and your shell

A few months ago I watched Claude Code run rm -rf on a directory it had misunderstood. Nothing critical was lost, but it got me thinking: I had given an autonomous agent unrestricted access to my shell, and the only thing standing between it and my filesystem was... nothing.

So I built Aegis — a small Rust CLI that sits between an AI agent and your real shell, and checks every command before it runs.

The problem

AI coding agents are genuinely useful, but they move fast and they make mistakes. The failure modes are well known by now:

  • deleting files or whole directories they misidentified
  • resetting or force-pushing git history
  • dropping database tables
  • publishing packages you didn't mean to publish

Most agents have their own permission prompts, but those live inside the agent. One misconfigured "auto-approve" setting, one YOLO mode session, and there's nothing left between the model and your data.

What Aegis does

Aegis intercepts every command and classifies it into one of four risk levels:

Level What happens
Safe Runs immediately — no delay, no prompt
Warn Pauses and asks for your approval
Danger Takes a best-effort snapshot first, then asks
Block Refused outright — no prompt

ls, cargo build, git status — these run instantly. The classification happens in under 2 milliseconds, so on the happy path you never notice Aegis is there.

But when the agent tries something like rm -rf ~/.config, you get this instead of silent data loss:

⚠ DANGER — FS-001 · Recursive delete
Command rm -rf ~/.config
Risk Danger
Pattern FS-001 — rm with -rf flag
Snapshot git stash created (a3f9b12)

[A] approve [D] deny [i] info

Note the snapshot line: before a dangerous command even asks for approval, Aegis takes a best-effort snapshot (git stash, or a Docker/SQLite-aware backup depending on context). If you approve the command and regret it, you can roll back. If you deny it, the snapshot is retained anyway.

Everything — every command, every decision, every snapshot — goes into an append-only, integrity-checked audit log. You can always answer the question "what exactly did the agent do yesterday?"

How it hooks in

For Claude Code and Codex, Aegis registers PreToolUse hooks, so every Bash command the agent runs goes through the scanner regardless of what $SHELL is set to:

aegis install-hooks --all
Enter fullscreen mode Exit fullscreen mode

For other agents that respect $SHELL, there's a shell-proxy mode:

aegis setup-shell
Enter fullscreen mode Exit fullscreen mode

This points SHELL at the aegis binary and keeps your real shell in AEGIS_REAL_SHELL. Aegis parses the command, classifies it, and only forwards to your real shell what you approved.

The interesting engineering bits

The hot path is Aho-Corasick, not regex. The first pass over every command is a multi-pattern scan with zero allocations. Regex only kicks in for commands that trip the fast scan. That's how safe commands stay under 2 ms.

Shell parsing is where the real work is. Agents don't write polite one-liners. They write heredocs, inline scripts, pipes, bash -c inside bash -c, and quoting that would make POSIX blush. Aegis has its own parser for this, and it's fuzzed in CI — because a guardrail with a parser bypass is worse than no guardrail at all. Some of the bypasses we've already fixed: uppercase variations, $IFS obfuscation, SQL hidden inside psql/mysql invocations.

Hooks fail closed. If the aegis binary goes missing or a hook dependency breaks, the hook denies the command instead of silently letting it through. A safety tool that fails open is just decoration.

Config can only get stricter. A per-project .aegis.toml can tighten the security posture but never weaken it. So a repository you cloned (or an agent editing files in it) can't quietly turn the guardrails off.

What Aegis is not

I want to be honest here, because security tools that oversell themselves are a plague: Aegis is a heuristic guardrail, not a sandbox. It is not a privilege boundary. A determined attacker — or a sufficiently creative agent — can construct commands that evade pattern-based classification. There's a full threat model (https://github.com/IliasAlmerekov/aegis/blob/main/docs/threat-model.md) in the repo that spells out exactly what's in and out of scope.

What Aegis protects you from is the realistic everyday case: an agent making an honest mistake at machine speed. In my experience, that covers the vast majority of near-disasters.

Try it

curl -fsSL https://raw.githubusercontent.com/IliasAlmerekov/aegis/main/scripts/install.sh | sh
Enter fullscreen mode Exit fullscreen mode

Or via npm / Homebrew if you prefer your installs package-managed:

npm i -g @iliasalmerekov/aegis
Enter fullscreen mode Exit fullscreen mode

or

brew tap IliasAlmerekov/aegis && brew install aegis
Enter fullscreen mode Exit fullscreen mode

Then verify it's working:

aegis -c 'echo hello'              # safe — runs instantly
aegis -c 'rm -rf /tmp/aegis-test'  # danger — prompt appears, press D
Enter fullscreen mode Exit fullscreen mode

Linux, macOS, and WSL2 are supported (no native Windows build).
GitHub (https://github.com/IliasAlmerekov/aegis) — issues and PRs welcome, especially bypass reports. If you can sneak a dangerous command past the scanner, I genuinely want to know.

Top comments (4)

Collapse
 
dipankar_sarkar profile image
Dipankar Sarkar

The snapshot-before-danger and the append-only log are the parts that survive contact with a real agent. The classifier is the part I'd worry about: pattern-matching argv is an arms race you lose. rm -rf trips FS-001, but find . -delete, git clean -fdx, a heredoc'd script, or curl x | sh all delete without ever matching an rm rule. The moment classification is regex-on-command, a model that just phrases the destructive step differently walks straight past it, and it doesn't even have to be adversarial to do that.

The version that holds up keys on effect, not syntax: does this unlink a path outside an allowed root, does it write, does it open a network egress. That costs real setup (syscall interposition, or running the command in a sandbox you diff), so the honest design is probably both: cheap string rules on the happy path for instant feedback, plus an effect-level backstop for the danger tier where a false negative actually loses data. The snapshot line is a smart hedge for exactly that gap.

Collapse
 
iliasalmerekov profile image
Ilias Almerekov

You're right on the principle—pattern-matching is an arms race.

To clarify, Aegis actually catches all four of your examples today (via specific rules for danger/warn/pipes), as well as deferred shapes like bash -c and python -c.

But your core point stands: sh ./cleanup.sh sails straight through. No pattern list can fix that.

That’s why the "both" approach is our 1.0 direction: cheap string rules for instant feedback, backed by kernel sandboxing (bwrap + Landlock) and snapshots for the danger tier.

Collapse
 
dipankar_sarkar profile image
Dipankar Sarkar

The audit log is the piece I'd keep no matter what. But the classifier has a bypass surface worth naming: it matches the spelling of a command, not its effect. rm -rf ~/.config is easy to catch. bash -c "$X", a base64 string piped to sh, a python one-liner calling os.unlink, or a symlink swapped in after the check all reach the same syscall while looking nothing like FS-001. An agent doesn't have to be adversarial to hit these. It just has to be creative about how it spells the task.

The complementary boundary sits one level down: gate the effect (unlink of a path outside an allowed root) instead of the string. That costs setup and latency, since syscall interposition is real overhead and full sandboxing more so. So the honest design is probably both. Cheap string classification on the happy path for instant feedback, plus a kernel-level backstop for the danger tier where a false negative actually deletes data. The snapshot-before-danger-prompt move is a smart hedge in the meantime.

Collapse
 
iliasalmerekov profile image
Ilias Almerekov

Hi👋, thanks for your feedback.

Spot on — the classifier matches spelling, not effect, and everything you listed (bash -c "$X", base64-to-sh, a python os.unlink, the symlink TOCTOU) sails right past it.

The effect-level backstop you describe actually already exists as an opt-in layer - crates/aegis-sandbox does bubblewrap + Landlock confinement on Linux. Making it default-on for the danger tier (and loud when it degrades) is on the 1.0 path.

The symlink-swap case is exactly what my bypass corpus is missing — mind opening an issue?😊

Would love to get it on the repo.🙆‍♂️