Fence blocks the catastrophic command before your agent runs it — and stays silent for everything else.
·
· CLI · Rules · Architecture
An agent is asked to send your AWS credentials to a URL. It reaches for the tool
call — cat ~/.aws/credentials | curl … — and Fence blocks it before it runs,
so the agent backs off. No denylist to evade: Fence parses the command and judges
what it actually does.
AI agents run with your permissions. A confused — or prompt-injected — agent
can delete your files, leak your keys, or wire up persistence, with nothing
standing between it and your machine. The denylist "guardrails" floating around
are substring matchers: trivially dodged (rm -fr, a script written then run),
and so noisy you turn them off.
Fence is built the other way:
- 🧠 Semantic, not substring.
rm -rf ~,rm -fr ~,rm -r -f ~,sudo rm -rf $HOME— one dangerous intent, all caught. - 🎯 Near-zero false positives.
rm -rf node_modules,git push --force-with-lease,npm install— never touched. This is the product. - 🚦 Block, ask, or allow. Unambiguous catastrophe is blocked; the plausibly-legit gets a confirm prompt; the everyday passes in silence.
- 🔒 Permissive modes don't weaken it. Agent hooks run before the
permission system, so a fence
denyblocks even in auto-accept or--dangerously-skip-permissionssessions — where it's the only guardrail left standing. - 🪶 Fails open. If Fence can't parse something, the command runs. A guardrail must never brick the agent it protects.
- 🧩 Agent-neutral. One portable rulepack. Claude Code, Codex, and OpenCode today; Cursor and Gemini next.
It stops prompt injection, not just clumsy commands. A hidden instruction in
a file steers the agent into rm -rf ~ — Fence blocks the tool call and tells you
where it came from.
More scenarios — asks when unsure · stays quiet on routine work · keeps secrets out of the model
Asks before an irreversible action — force-push, history rewrite:
Stays out of the way on everyday commands — near-zero false positives:
Keeps secrets out of the model's context — even a cat with no network:
# Homebrew — macOS
brew install hoophq/tap/fence# npm — macOS / Linux
npm install -g @hoophq/fenceWindows isn't supported natively yet — the hook path hasn't been verified there, and a silently broken hook is worse than an honest no. WSL works today (Fence behaves exactly as on Linux inside it); native support is tracked in #26.
fence init --global # add the Fence hooks to .claude/settings.json, for every projectStart a Claude Code session and Fence is live — a status line at the bottom
of the UI confirms it, for the whole session (🚧 Fence v1.2.0 · 1 pack · 19 rules). Already have a status line? Fence won't touch it — it announces
itself with a chat banner instead. Ask the agent for something reckless — it
gets stopped, or asked to confirm, with a 🚧 notice in the chat saying which
rule fired. Allowed calls get a notice too, so you can see Fence watching;
fence init --quiet turns those off.
claudeAnd here's how you leave: fence uninstall removes exactly the hooks init
added and touches nothing else. Dev-owned means you can walk away cleanly.
Same guardrails, same rulepacks, one command:
fence init codex # writes ./.codex/hooks.json (--global for ~/.codex)Then run /hooks inside Codex once to trust the Fence entries (Codex only
runs hooks you've approved). Shell commands and apply_patch file edits are
screened by the same engine — one rulepack, every agent.
fence init opencode # writes ./.opencode/plugins/fence.js (--global for ~/.config/opencode)OpenCode has no hook protocol, so init generates a tiny plugin that pipes
every shell command and file edit to the same engine. One honest caveat:
OpenCode plugins can block a call but can't show an approval prompt, so ask
rules stop the call and route the agent to you for confirmation instead.
→ All CLI commands — init/uninstall, check (test a
verdict without an agent), add/search (rulepacks), hook, and version.
The recommended pack is embedded in the binary and always on:
| It stops an agent from… | like | |
|---|---|---|
| wiping your home or root | rm -rf ~ · sudo rm -rf / |
🛑 deny |
| wiping a disk | dd of=/dev/sda · mkfs.ext4 /dev/sdb1 |
🛑 deny |
| detonating a fork bomb | :(){ :|:& };: |
🛑 deny |
| exfiltrating a secret | cat ~/.ssh/id_rsa | curl … |
🛑 deny |
| opening up a system path | chmod -R 777 / |
🛑 deny |
| deleting outside your workspace | rm -rf ~/.config/x |
ask |
| reading a key into its context | cat ~/.aws/credentials |
ask |
| piping the web into a shell | curl … | sh |
ask |
| rewriting git history | git push --force · git reset --hard |
ask |
| installing off-registry | npm i git+https://… · pip install git+… |
ask |
| injecting an install hook | a postinstall added to package.json |
ask |
| setting up persistence | crontab - · a LaunchAgent · systemctl enable |
ask |
…and it is not fooled by flag reordering, sudo, $HOME vs ~, or a
renamed fork bomb. Every detector ships with tests that pin both the catch and
the safe cases.
→ Write your own rules & the full match reference
Layer your own rules with a ./.fence.yaml (auto-discovered) or --rules <file>:
rules:
- id: no-terraform-destroy
effect: deny
match:
shell: { command_in: [terraform] }
regex: '\bterraform\b.*\bdestroy\b'Or retune a built-in rule's effect in a single line — no need to redefine it:
overrides:
git-force-push: deny # ask -> deny
pipe-to-shell-from-network: allow # silence itGuardrails others already wrote — Terraform, Kubernetes, production databases — install with one command and are active on the next tool call, in every project:
fence search # see what's published
fence add terraform-safety # checksum-verified, then live everywhereCompose them in a committed .fence.yaml (extends: [terraform-safety]) to
pin a baseline for your whole team — and publishing your own pack is just a PR.
→ The registry: install, author, publish
agent tool call → adapter → engine (shell-AST facts) → rulepack → allow · ask · deny
An adapter normalizes each agent's tool call into a neutral action; the engine parses shell commands into semantic facts; rules match those facts and the most severe effect wins. The engine and rulepacks know nothing about any specific agent — which is what makes one rulepack portable across all of them.
→ Architecture & extension points
Fence is local self-protection: it lives in your config, and you can edit or remove it. That's exactly right for protecting yourself from an agent's mistakes. It is honestly not a compliance control — a determined user (or an agent running as you) can disable anything on a machine they fully control. The threat model spells this out, including the known evasion paths and why they're accepted.
Need guardrails your developers can't turn off — centrally managed, enforced fleet-wide, with approval workflows and audit? That's a different trust model, and it's what hoop.dev does. Same idea, enforced where the developer can't override it.
- Semantic detectors — deletes, disk wipes, fork bombs, exfiltration, world-writable, off-registry installs, manifest hooks, persistence
- One-line installers — Homebrew, npm
- A shareable rulepack registry —
fence search·fence add <pack>· publish your own - A second agent — Codex (same rulepacks, zero engine changes)
- A third — OpenCode (via its plugin surface, engine still untouched)
- More agents — Cursor, Gemini CLI
MIT © hoop.dev — built by the team behind hoop.