https://github.com/hoophq/fence
Guardrails for AI coding agents β block catastrophic tool calls before they run. Semantic, agent-neutral, near-zero false positives.
https://github.com/hoophq/fence
ai-agents ai-safety claude-code cli developer-tools go guardrails security
Last synced: 2 days ago
JSON representation
Guardrails for AI coding agents β block catastrophic tool calls before they run. Semantic, agent-neutral, near-zero false positives.
- Host: GitHub
- URL: https://github.com/hoophq/fence
- Owner: hoophq
- License: mit
- Created: 2026-06-30T17:04:19.000Z (14 days ago)
- Default Branch: main
- Last Pushed: 2026-07-06T19:40:14.000Z (8 days ago)
- Last Synced: 2026-07-06T21:13:46.657Z (8 days ago)
- Topics: ai-agents, ai-safety, claude-code, cli, developer-tools, go, guardrails, security
- Language: Go
- Homepage: https://hoop.dev/?utm_source=fence&utm_medium=github&utm_campaign=att-launch-072026
- Size: 1.19 MB
- Stars: 14
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README
# π§ Fence
### Guardrails for AI coding agents.
Fence blocks the catastrophic command **before** your agent runs it β
and stays silent for everything else.
[](https://github.com/hoophq/fence/actions/workflows/ci.yml)
Β Β·Β 
Β Β·Β [CLI](docs/cli.md) Β Β·Β [Rules](docs/rules.md) Β Β·Β [Architecture](docs/architecture.md)

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_.
---
## Why Fence
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 `deny` blocks even in auto-accept or
`--dangerously-skip-permissions` sessions β 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.
---
## See it in the loop
**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:

---
## Install
```bash
# Homebrew β macOS
brew install hoophq/tap/fence
```
```bash
# npm β macOS / Linux
npm install -g @hoophq/fence
```
**Windows** 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](https://github.com/hoophq/fence/issues/26).
## Quickstart β Claude Code
```bash
fence init --global # add the Fence hooks to .claude/settings.json, for every project
```
Start a Claude Code session and Fence is live β a banner in the chat confirms
it (`π§ Fence is guarding this sessionβ¦`). 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.
```bash
claude
```
And 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.
## Quickstart β Codex
Same guardrails, same rulepacks, one command:
```bash
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.
## Quickstart β OpenCode
```bash
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](docs/cli.md)** β `init`/`uninstall`, `check` (test a
verdict without an agent), `add`/`search` (rulepacks), `hook`, and `version`.
---
## What it stops
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](docs/rules.md)**
---
## Make it yours
Layer your own rules with a `./.fence.yaml` (auto-discovered) or `--rules `:
```yaml
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:
```yaml
overrides:
git-force-push: deny # ask -> deny
pipe-to-shell-from-network: allow # silence it
```
**β [Rules & overrides, in depth](docs/rules.md)**
---
## Grab a rulepack
Guardrails others already wrote β Terraform, Kubernetes, production databases β
install with one command and are active on the next tool call, in every project:
```bash
fence search # see what's published
fence add terraform-safety # checksum-verified, then live everywhere
```
Compose 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](docs/registry.md)**
---
## How it works
```
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](docs/architecture.md)**
---
## What Fence is β and isn't
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](docs/threat-model.md)** 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](https://hoop.dev/start?utm_source=fence&utm_medium=github&utm_campaign=att-launch-072026)** does. Same idea, enforced where the
developer can't override it.
---
## Roadmap
- [x] Semantic detectors β deletes, disk wipes, fork bombs, exfiltration,
world-writable, off-registry installs, manifest hooks, persistence
- [x] One-line installers β Homebrew, npm
- [x] A shareable rulepack registry β `fence search` Β· `fence add ` Β·
[publish your own](docs/registry.md)
- [x] A second agent β Codex (same rulepacks, zero engine changes)
- [x] A third β OpenCode (via its plugin surface, engine still untouched)
- [ ] More agents β Cursor, Gemini CLI
## License
MIT Β© [hoop.dev](https://hoop.dev/?utm_source=fence&utm_medium=github&utm_campaign=att-launch-072026) β built by the team behind hoop.