Skip to content

Releases: hoophq/fence

v1.4.0

Choose a tag to compare

@github-actions github-actions released this 03 Aug 17:48
31623d4

Scratch space stops asking 🚧

Agents write to temp directories constantly — build output, checkouts, intermediate files — and clean up after themselves just as often. Fence treated every one of those cleanups as a recursive delete outside the workspace and asked. rm -rf /tmp/agent-build is not a catastrophe, and prompting on it is exactly the false positive that gets a guardrail uninstalled.

Deletes inside a temp directory now pass in silence: /tmp/build, /var/tmp/x, /private/tmp/y, macOS /var/folders/…/T/z, and $TMPDIR/scratch. A prefixed glob like /tmp/build-* still names a specific target, so it counts too.

What still asks

The temp root itself and a bare wildcard sweep of it — rm -rf /tmp, rm -rf /tmp/*, rm -rf $TMPDIR — wipe every process's scratch state rather than one named target, so those keep prompting. So do lookalikes (/tmpfoo/bar), the macOS cache sibling (/var/folders/…/C/x), and traversals that escape the root (/tmp/../etc is really /etc). A temp operand never launders a dangerous one beside it: rm -rf /tmp/scratch ~ is still a deny.

$TMPDIR is resolved to its actual value before the path is judged, so it counts as scratch only when it really points into a temp directory. If TMPDIR is unset — common on Linux — the shell expands rm -rf $TMPDIR/scratch to rm -rf /scratch, and Fence still asks. A project that itself lives under /tmp classifies its own files as workspace-local, not scratch.

New in the rule schema

delete_target and chmod_target accept a new temp value, the least-severe rung of the shared path scale (temp < cwd_relative < outside_workspace < sensitive). Nothing in the recommended pack matches temp, so scratch deletes fall through to the default allow. Match it explicitly to opt back into prompting:

rules:
  - id: confirm-temp-deletes
    effect: ask
    match:
      shell:
        recursive_delete: true
        delete_target: temp

See the match reference for the full scale.

Upgrading

Nothing to do — brew upgrade fence or your usual install path. No config or rulepack changes are required, and no existing rule changes meaning.

Changelog

  • 5d9bc6e feat: stop prompting on recursive deletes inside temp dirs
  • a740405 fix: resolve $TMPDIR before trusting it as scratch space (an unset TMPDIR made $TMPDIR/x mean /x), and rank workspace-local above scratch for projects living under /tmp

Full diff: v1.3.0...v1.4.0

v1.3.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 20:12
0c9b351

One hook per agent 🚧

A Claude Code plugin can register a Fence hook of its own — the Hoop plugin ships one. On a machine with both the plugin and a fence init install, Fence evaluated every tool call twice: same verdict, doubled latency, every "🚧 Fence allowed this" notice printed twice.

fence init now detects an enabled plugin that already runs the Fence hook and installs only the status line — the piece no plugin can provide — and removes a duplicate hook an earlier init left behind. Detection deliberately fails toward installing: a false negative merely duplicates work, but a false positive would leave you unguarded, so every uncertainty resolves to "install it". A --global install always writes the hooks, since any single project can disable a plugin out from under it.

New flags

  • fence init --statusline-only — write just the status line, never the hooks
  • fence init --force-hooks — install Fence's own hook even beside a plugin's
  • fence uninstall --hooks-only — remove the hooks, keep the status line (plain uninstall still removes both)

Upgrading

If you run both the Hoop plugin and a fence init install, upgrade and re-run fence init once — it removes the duplicate and keeps your status line. /hoop:doctor (plugin ≥ 0.7.2) now detects real duplicates only and recommends the version-appropriate fix.

Changelog

  • ad839ea init: stand down when a Claude Code plugin already runs the Fence hook
  • efad8c2 fix: --global init never stands down (a project could disable the plugin and be left unguarded)
  • f2811e1 fix: reject --statusline-only/--force-hooks for agents without a status line
  • ba403f0 fix: wrapper-script detection requires an actual invocation line, not a mention
  • 0e6278a, 607abb9 ci: GitHub→Linear issue sync

Full diff: v1.2.0...v1.3.0

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 14 Jul 19:57
15367c7

🚧 The barrier is now always on screen

Fence's proof of protection in Claude Code is no longer a chat message that scrolls away — it's a persistent status line at the bottom of the UI, for the whole session:

🚧 Fence v1.2.0 · 1 pack · 19 rules

If rulepacks fail to load, the line says so instead of showing a quietly lower count (— ⚠️ 1 rulepack failed to load), and if Fence can't load its rules at all, it tells you plainly that tool calls are not being screened. A session without the barrier on screen isn't guarded — now you can tell at a glance, the entire time.

Plays nice with your own status line

The statusLine slot is yours, and Fence never takes it from you:

  • Already have a status line — in the target settings, your user-level ~/.claude/settings.json, or settings.local.json? fence init leaves it untouched and announces through the SessionStart chat banner instead, telling you how to combine the two (append the output of fence hook claude-code statusline from your own statusline command).
  • Configured your own status line after Fence installed its? The next fence init steps aside automatically.
  • fence uninstall removes Fence's status line and never anyone else's.

Upgrading

The status line arrives on your next fence init re-run:

brew upgrade fence        # or: npm update -g @hoophq/fence
fence init --global       # or per-project: fence init

Existing installs keep working unchanged (the SessionStart banner still ships in the binary) — re-running fence init migrates them to the status line. Codex and OpenCode keep their session banners; neither has a status-line surface.

Full Changelog: v1.1.0...v1.2.0

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 10 Jul 18:59
6f76918

OpenCode support 🚧

The same rulepacks now guard OpenCode — the third agent, with zero engine changes:

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: bash, edit, write, read, webfetch, and apply_patch (screened per file touched, most severe verdict wins). The session banner confirms Fence is watching, and fence uninstall opencode is the exit door.

Two honest notes:

  • 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 of prompting natively.
  • Fail-open holds on both sides: if the fence binary is missing or errors, the plugin lets the call proceed — a guardrail must never brick the agent it protects.

Also in this release

  • The threat-model and architecture docs now describe the hooked surface per agent instead of assuming Claude Code.

Install: brew install hoophq/tap/fence · npm install -g @hoophq/fence · binaries below
Upgrading? The binary alone is enough for existing Claude Code/Codex setups — nothing to re-run. For OpenCode, run fence init opencode once.

Fence 1.0.0 — the compatibility promise

Choose a tag to compare

@github-actions github-actions released this 06 Jul 19:05
a999976

⚠️ Renamed: Leash is now Fence 🚧

Clean break, no compatibility shims. If you had leash installed:

leash uninstall --global        # remove the old hooks (with the old binary)
brew uninstall leash            # or: npm rm -g @hoophq/leash
brew install hoophq/tap/fence   # or: npm install -g @hoophq/fence
fence init --global

State lives in ~/.fence now; project rule files are ./.fence.yaml. Installed packs: re-run fence add <pack>.

What 1.0 promises

1.0 is a compatibility promise, not a feature bar. The three public contracts — the rulepack schema, the registry index format, and the hook envelope — are frozen and versioned for all of 1.x (what that means). Rulepacks declare schema: 1; a pack needing newer vocabulary is refused whole on older binaries (skipped with an "upgrade fence" warning), never half-read.

New since 0.2.0

  • Codex support — the same rulepacks now guard OpenAI Codex CLI with zero engine changes: fence init codex, then trust the hook once via /hooks inside Codex. Shell commands and apply_patch edits are screened per file touched.
  • fence uninstall [agent] — the exit door: removes exactly the hooks init added, preserves everything else.
  • A written threat modelwhat Fence defends against, what it deliberately doesn't, and the known evasion paths, honestly.
  • Community scaffolding — CONTRIBUTING, SECURITY (private vulnerability reporting), and issue templates; the false-positive template is the one that matters most.
  • Windows: honestly unsupported natively — fence init says so instead of half-working; WSL works today. #26 tracks native support.

Install: brew install hoophq/tap/fence · npm install -g @hoophq/fence · binaries below

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 03 Jul 19:27
8ce1814

Allowed-call notices are now on by default

The 🐕 Leash allowed this chat notice — previously opt-in via leash init --verbose — is now the default. It's how you see Leash watching, not just interrupting.

  • Nothing to do on upgrade: the new default lives in the hook binary itself, so existing installs pick it up with the binary — no re-run of leash init needed.
  • Prefer the old quiet behavior? leash init --quiet (per project) or leash init --global --quiet.
  • --verbose remains accepted everywhere for backward compatibility; on leash init it prints a deprecation nudge pointing at --quiet.
  • Unchanged: allow notices never carry a permission decision, so your own permission settings still apply.

Changelog

  • eed4406 Make allowed-call chat notices the default; add --quiet to opt out

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 03 Jul 18:16
a41bb46

Leash is now visible in the chat

  • 🐕 Session banner — every Claude Code session opens with Leash v0.1.0 is guarding this session (N packs, M rules). If an installed rulepack or .leash.yaml fails to load, the banner says so instead of silently showing a lower count.
  • Decision notices — deny, ask, and warn decisions post a one-line 🐕 notice in the chat naming the rule that fired. Allowed calls stay silent.
  • Verbose modeleash init --verbose announces allowed calls too. Feedback rides systemMessage only; Leash never emits an explicit allow decision, so your own permission prompts still apply.
  • leash init now converges both hooks in one pass: it heals stale binary paths, toggles --verbose, and preserves anything you added by hand (custom matchers, a --rules flag).

Upgrading from 0.0.x? The decision notices arrive with the binary alone. To get the session banner, re-run leash init (or leash init --global) once — it adds the new SessionStart hook and leaves the rest of your settings untouched.

Changelog

  • 046e12a Make Leash visible in chat: session banner + decision notices

v0.0.4

Choose a tag to compare

@github-actions github-actions released this 03 Jul 13:10
11f62c0

The rulepack registry 📦

Rulepacks are now a shareable, installable artifact. Someone writes the guardrails once — Terraform, Kubernetes, production databases — and everyone else gets them with one command:

$ leash search
$ leash add terraform-safety
Installed terraform-safety 1.0.0 (5 rules) — active on every tool call from now on.

$ leash check 'terraform destroy'
  DENY   terraform destroy
  rule: terraform-destroy (critical) · from terraform-safety
  • leash add <pack> — sha256-verified against the registry index before anything touches disk; live immediately in every project, no restart, no per-project setup. leash update and leash remove round it out.
  • extends: composition — a committed .leash.yaml can pin a team baseline (extends: [terraform-safety]) on top of your own rules and overrides.
  • Three packs to start: terraform-safety, prod-db-guard, k8s-safety — each holding the same near-zero-false-positive line as the built-in pack (terraform plan -destroy, kubectl uncordon, and local psql all stay silent).
  • Publishing is a PR — add a YAML file + an index entry to the repo and it's live on merge. Self-host with --registry <url or path>. Authoring & publishing guide →

Hardening that shipped with it: a broken installed pack (or .leash.yaml) is now skipped with a warning instead of taking the whole engine down, leash check names the pack behind every decision, leash update refuses to let one registry replace a pack installed from another, and nothing on the evaluation path can ever touch the network.

Installbrew install hoophq/tap/leash · npm install -g @hoophq/leash — then leash init --global.

v0.0.3

Choose a tag to compare

@github-actions github-actions released this 02 Jul 14:07
ce533ed

Leash 0.0.3 — your Claude Code hook now survives brew upgrade.

# Homebrew — macOS
brew install hoophq/tap/leash
# npm — macOS / Linux
npm install -g @hoophq/leash

Fixed

  • The hook no longer dies on upgrade. leash init used to resolve symlinks when recording the hook command, so Homebrew installs wrote the version-pinned Caskroom/leash/<version>/leash path — and the next brew upgrade deleted it, silently disarming Leash (it fails open) until re-init. Init now records the stable /opt/homebrew/bin/leash symlink, which upgrades repoint.
  • leash init now heals. If your settings already carry a Leash hook with a stale or different command, re-running leash init updates it in place ("Updated the Leash hook command") instead of reporting "already present".

If you installed ≤ 0.0.2 via Homebrew and ran leash init: run leash init (or leash init --global) once after upgrading — it will repair the hook path for you.

Changelog

  • fe47dd4 Write an upgrade-stable path into the Claude Code hook, and heal stale ones

v0.0.2

Choose a tag to compare

@github-actions github-actions released this 02 Jul 13:25
20802e1

Leash 0.0.2 — the npm path goes live, and brew install now works first-run on macOS.

# Homebrew — macOS
brew install hoophq/tap/leash
# npx — macOS / Linux, no install
npx @hoophq/leash

What's new

  • Smoother macOS install — the Homebrew cask now strips the quarantine attribute in a postflight, so the binary runs immediately after install instead of being killed by Gatekeeper (proper signing + notarization is on the roadmap).
  • Shell rc/profile files are treated as env-tier secrets — reading or exfiltrating ~/.zshrc, ~/.bashrc, ~/.profile and friends now gets the same scrutiny as .env files, since rc files routinely export API keys.
  • npm distribution@hoophq/leash ships prebuilt binaries for macOS/Linux on x64/arm64 via the esbuild-style optionalDependencies pattern: no postinstall script, no runtime download.

Changelog

  • d034288 Classify shell rc/profile files as env-tier secrets
  • 18394a0 Strip quarantine in the cask postflight — Gatekeeper kills the unsigned binary