108 lines
4.4 KiB
Markdown
108 lines
4.4 KiB
Markdown
# security-hooks
|
|
|
|
Defense-in-depth security hooks for AI coding agents. Works with **Claude Code**, **Gemini CLI**, and **Codex**.
|
|
|
|
A single daemon evaluates every tool call against configurable rules — blocking destructive commands, catching data exfiltration, and flagging suspicious behavior before it reaches your system.
|
|
|
|
## How it works
|
|
|
|
```
|
|
AI Agent ──► Rust Shim ──► Unix Socket ──► Elixir Daemon ──► Verdict
|
|
(<1ms) (<1ms) (rule engine) allow/deny/ask
|
|
```
|
|
|
|
1. The AI tool calls a hook before each tool use (bash command, file edit, MCP call)
|
|
2. A tiny Rust binary forwards the payload to a long-running Elixir daemon
|
|
3. The daemon evaluates rules using **two layers**:
|
|
- **Regex** — fast pattern matching for obvious threats (fork bombs, miners)
|
|
- **AST** — structural analysis via tree-sitter-bash that catches evasion (`rm -rf $(echo /)`, piped exfiltration, obfuscated commands)
|
|
4. Returns `allow`, `deny` (with a nudge message), or `ask` (falls through to human approval)
|
|
|
|
The system is **fail-closed** — if the daemon is unreachable, the tool call is blocked.
|
|
|
|
## Threat coverage
|
|
|
|
- **Prompt injection** — malicious content in READMEs, web pages, or MCP responses can't trick the agent into running blocked commands
|
|
- **Destructive operations** — `rm -rf`, force push, `sudo`, cloud resource deletion, and more
|
|
- **Data exfiltration** — detects secrets being piped to `curl`/`nc`, reads of `~/.ssh` or `~/.aws`, env var leaks
|
|
- **Supply chain attacks** — flags dependency mutations, unknown executables, lockfile edits
|
|
- **MCP injection** — validates MCP server identity, scans parameters for shell injection via AST
|
|
|
|
## Rule DSL
|
|
|
|
Rules live in `.rules` files with a custom syntax designed for regex without escaping pain:
|
|
|
|
```
|
|
# Regex — pattern is literal to end of line, no quoting needed
|
|
block "fork-bomb"
|
|
match :\(\)\s*\{.*\|.*&\s*\}\s*;
|
|
nudge "Fork bomb detected"
|
|
|
|
# AST — structural matching that catches evasion
|
|
block "destructive-rm"
|
|
match command("rm") with_flags("-r", "-rf", "-fr")
|
|
nudge "Use trash-cli or move to a temp directory"
|
|
|
|
block "pipe-to-exfil"
|
|
match pipeline_to("curl", "wget", "nc")
|
|
nudge "Don't pipe output to network commands"
|
|
|
|
# Config-referenced allowlist
|
|
suspicious "unknown-executable"
|
|
match_base_command_not_in allowed_executables
|
|
nudge "Unknown command '{base_command}'. Add it to config.toml"
|
|
```
|
|
|
|
The rule loader auto-detects regex vs AST based on whether the match starts with a function like `command(`.
|
|
|
|
## Two tiers
|
|
|
|
- **block** — hard deny. The agent sees the nudge and self-corrects.
|
|
- **suspicious** — falls through to the human permission prompt with context.
|
|
|
|
## Configuration
|
|
|
|
```toml
|
|
# config.toml — defaults ship with the project
|
|
[executables]
|
|
allowed = ["git", "mix", "cargo", "go", "node", "npm", "python", "rg", "fd", ...]
|
|
|
|
[secrets]
|
|
env_vars = ["AWS_SECRET_ACCESS_KEY", "GITHUB_TOKEN", "DATABASE_URL", ...]
|
|
|
|
[paths]
|
|
sensitive = ["~/.ssh", "~/.aws/credentials", "~/.config/gcloud", ...]
|
|
```
|
|
|
|
Override with `config.local.toml` (gitignored):
|
|
|
|
```toml
|
|
[executables]
|
|
append = ["my-custom-tool", "deno"]
|
|
exclude = ["curl"]
|
|
|
|
[rules]
|
|
disabled = ["force-push"]
|
|
```
|
|
|
|
## Architecture
|
|
|
|
- **Rust shim** — all three AI tools invoke hooks by spawning a process, piping JSON to stdin, and reading JSON from stdout. That process has to exist, but the rule engine lives in a long-running Elixir daemon for hot-reload and sub-millisecond evaluation. The shim bridges the two: a ~1MB static Rust binary that connects to the daemon's Unix socket and relays the verdict. Rust because it starts in <1ms — bash has quoting bugs and needs `socat`, Elixir escript pays ~300ms BEAM boot per call, and a second Burrito binary would unpack on every cold invocation.
|
|
- **Elixir daemon** — distributed as a [Burrito](https://github.com/burrito-elixir/burrito) binary (no Erlang/Elixir install needed)
|
|
- **Adapter layer** — normalizes payloads across Claude Code, Gemini CLI, and Codex
|
|
- **tree-sitter-bash** — Rust NIF for robust AST parsing of shell commands
|
|
- **Hot-reload** — edit rules or config, changes apply on the next tool call
|
|
- **systemd/launchd** — socket activation for zero cold-start latency, automatic crash recovery
|
|
|
|
## Platforms
|
|
|
|
macOS (aarch64, x86_64) · Linux (x86_64, aarch64) · WSL
|
|
|
|
## Status
|
|
|
|
Design phase. See [`docs/specs/2026-03-26-security-hooks-design.md`](docs/specs/2026-03-26-security-hooks-design.md) for the full spec.
|
|
|
|
## License
|
|
|
|
TBD
|