A Claude Code plugin that governs your agent with deterministic, hook-level controls — a policy gate (deny / ask / allow per tool call), a tamper-evident audit trail, trust tiers, a kill switch, session budgets, and role-based multi-agent policy. Enforced in hooks, not prompts. Fails closed.
Governance that lives in a CLAUDE.md paragraph is advice. This plugin is a control: every tool call is evaluated in a PreToolUse hook — deterministic Python running outside the model — before it executes.
From GitHub (inside Claude Code):
/plugin marketplace add ichiorca/claude-code-guardrails
/plugin install claude-code-guardrails@claude-code-guardrails
From a local clone (CLI):
git clone https://github.com/ichiorca/claude-code-guardrails
claude plugin marketplace add ./claude-code-guardrails
claude plugin install claude-code-guardrails@claude-code-guardrailsBoth scopes work: the default (--scope user) governs every project on the machine; add --scope project to govern only the current project (enablement is written to that project's .claude/settings.json, so it travels with the repo).
On first install the plugin governs your project using its bundled default policy, so you're protected immediately. To customize, drop a guardrails.toml into your project root (see below).
Requires Python 3 on your
PATHas eitherpython3orpython— the hooks detect which one works (see Platform notes).
| Hook | Script | Job |
|---|---|---|
SessionStart |
session_brief.py |
Injects the active rules into context; stamps sponsor + tier + policy hash into the audit chain |
UserPromptSubmit |
prompt_guard.py |
Blocks prompts containing credential-shaped strings before the model sees them (fails closed) |
PreToolUse (matcher *) |
policy_gate.py |
The gate: kill switch → session budget → policy rules → deny / ask / allow. Fails closed |
PostToolUse (matcher *) |
audit_log.py |
Appends a hash-chained, lock-serialized audit record per executed call |
SubagentStart |
subagent_register.py |
Attributes each spawned subagent's role into the audit chain |
The matcher is *, so the kill switch and budget cover every tool — built-ins and MCP tools alike, not just a hardcoded list.
Policy is data — versioned, diffable, code-reviewable. Rules are evaluated top-to-bottom, first match wins.
[[rule]]
id = "no-destructive-shell"
tools = ["Bash"]
command_regex = 'rm ... -rf | git push .*--force | ...'
action = "deny" # deny | ask | allow
reason = "Destructive command. Requires a human."
[[rule]]
id = "subagents-no-shell"
agents = ["docs-writer", "researcher"] # role-scoped: least privilege per agent
tools = ["Bash"]
action = "deny"Where the policy comes from (first hit wins):
GUARDRAILS_POLICYenv var (explicit override)guardrails.tomlin your project root — the normal way to customize- the default
guardrails.tomlbundled with the plugin
A policy that can't be found or parsed makes the gate fail closed (block), never silently unguarded.
To customize: copy the plugin's guardrails.toml into your project, edit it, and run
python3 "$CLAUDE_PLUGIN_ROOT/scripts/validate_policy.py" (or just ask Claude to "check my guardrails" — see the skill).
- Trust tiers —
default_tier/GUARDRAILS_TIER. Inunattendedsessions (CI, cron) everyaskhardens todeny, because an escalation nobody can answer must fail closed. - Session budget —
limits.max_tool_calls_per_sessioncaps a runaway loop. - Kill switch — create
.guardrails/HALTand the gate blocks every call and stops the agent loop (continue: false). Delete it to resume. - Signed receipts —
scripts/issue_receipt.pyattests the chain head, record count, and policy hash; it refuses to attest a chain that doesn't verify.
| Component | Name | What it's for |
|---|---|---|
| Hooks | hooks/hooks.json (5 events) |
The enforcement itself — automatic |
| Skill | guardrails-audit |
"check my guardrails" / "verify the audit trail" / "red-team it" — inspect & verify |
| Skill | guardrails-policy |
"block kubectl delete" / "tighten my guardrails" / "scope a rule to a subagent" — author & test rules (with a rule cookbook) |
| Skill | guardrails-incident |
"halt the agent" / "engage the kill switch" / "investigate the session" — incident response |
| Skill | guardrails-report |
"generate a governance report" / "what did the agent do today" — compliance report from the trail |
| Agent | policy-reviewer |
"review my guardrails.toml" — qualitative coverage audit a linter can't do |
| Scripts | validate_policy, verify_audit, issue_receipt, redteam, audit_report, run_gauntlet |
CLI utilities (also runnable without Claude Code) |
| Examples | examples/{dev,strict,unattended-ci}.toml |
Ready-made policy postures — copy the closest |
| Docs | docs/POLICY.md |
Full guardrails.toml schema reference |
Skills trigger from natural language — just ask. The policy-reviewer agent runs when you ask for a policy review or say "is this policy strong enough?".
python3 demo/run_demo.py # watch allow/ask/deny, the kill switch, and tamper detection
python3 scripts/redteam.py # Attack Success Rate (must-block) + false-positive rate; both 0% in CI
python3 -m pytest -q # the test suiteOr run everything CI runs in one shot — each gate's transcript plus a
SUMMARY.md lands in a dated reports/gauntlet-<timestamp>/ folder, and the
exit code is nonzero if any gate fails:
python3 scripts/run_gauntlet.pyCI (.github/workflows/governance-ci.yml) gates every PR on policy validation, the tests, the audit + receipt self-tests, and the red-team gate (ASR and FPR must both be 0%).
- The gate matches commands with regex — a tripwire, not a sandbox. Obfuscated commands (
find -delete, a base64-piped payload) evade it; those are kept in the red-team suite as evasions on purpose. Run OS-level sandboxing (Claude Code's own, or container isolation) underneath this plugin — it is not a replacement for one. - The audit chain is tamper-evident, not tamper-proof: editing it is always detectable, but a party with filesystem access could delete the whole trail. Signed receipts narrow that window — issue them at checkpoints and store them elsewhere.
- Receipts sign with symmetric HMAC: whoever can verify can also sign. Fine for self-attestation and CI; for receipts a third party should trust, switch to asymmetric signatures (the receipt format is unchanged).
- Role scoping keys on the
agent_typefield of the hook payload, which is not cryptographically proven — it is least-privilege hygiene, not an identity system.
- The hooks probe for
python3and automatically fall back topython(seehooks/hooks.json), so they work on Windows machines wherepython3is missing or is the dead Microsoft Store stub. One of the two launchers must resolve in the shell Claude Code runs hooks from — quick check:python --versionin Git Bash. - The bundled scripts run fine with either launcher:
python scripts/run_gauntlet.pyworks the same aspython3 .... - When redirecting script output to a file on Windows, set
PYTHONUTF8=1— the console default (cp1252) can't encode the report's ✅ marks.run_gauntlet.pysets this for you.
Policy and code changes are both gated the same way:
- Make the change (for rules: edit
guardrails.toml, and add a must-block and a benign near-miss case toscripts/redteam.py). - Run
python3 scripts/run_gauntlet.py— every CI gate locally, report set underreports/. - Open a PR;
governance-ci.ymlenforces the same gates, including ASR/FPR = 0%.
Packaging changes (hooks/hooks.json, .claude-plugin/) should additionally pass claude plugin validate . and show all five hooks in claude plugin details. Version history lives in CHANGELOG.md; to report a vulnerability see SECURITY.md.