An open API service indexing awesome lists of open source software.

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.

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.

[![CI](https://github.com/hoophq/fence/actions/workflows/ci.yml/badge.svg)](https://github.com/hoophq/fence/actions/workflows/ci.yml)
Β Β·Β  ![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)
Β Β·Β  [CLI](docs/cli.md) Β Β·Β  [Rules](docs/rules.md) Β Β·Β  [Architecture](docs/architecture.md)

An agent is asked to exfiltrate AWS credentials; Fence blocks the tool call before it runs

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.

A prompt injection hidden in a Makefile tries to make the agent delete your home directory; Fence blocks it

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:

Fence pauses a force-push and asks the human to confirm

**Stays out of the way on everyday commands** β€” near-zero false positives:

Fence lets rm -rf node_modules and npm install run without a prompt

**Keeps secrets out of the model's context** β€” even a `cat` with no network:

Fence asks before an agent reads cloud credentials into its context

---

## 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.