Skip to content

ichiorca/claude-code-guardrails

Repository files navigation

claude-code-guardrails

governance-ci License: MIT

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.

Install

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-guardrails

Both 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 PATH as either python3 or python — the hooks detect which one works (see Platform notes).

What it enforces

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.

The policy: guardrails.toml

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):

  1. GUARDRAILS_POLICY env var (explicit override)
  2. guardrails.toml in your project root — the normal way to customize
  3. the default guardrails.toml bundled 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).

Production primitives

  • Trust tiersdefault_tier / GUARDRAILS_TIER. In unattended sessions (CI, cron) every ask hardens to deny, because an escalation nobody can answer must fail closed.
  • Session budgetlimits.max_tool_calls_per_session caps a runaway loop.
  • Kill switch — create .guardrails/HALT and the gate blocks every call and stops the agent loop (continue: false). Delete it to resume.
  • Signed receiptsscripts/issue_receipt.py attests the chain head, record count, and policy hash; it refuses to attest a chain that doesn't verify.

What's included

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?".

Verify it yourself (no Claude Code needed)

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 suite

Or 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.py

CI (.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%).

Honest limitations

  • 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_type field of the hook payload, which is not cryptographically proven — it is least-privilege hygiene, not an identity system.

Platform notes

  • The hooks probe for python3 and automatically fall back to python (see hooks/hooks.json), so they work on Windows machines where python3 is 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 --version in Git Bash.
  • The bundled scripts run fine with either launcher: python scripts/run_gauntlet.py works the same as python3 ....
  • 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.py sets this for you.

Contributing

Policy and code changes are both gated the same way:

  1. Make the change (for rules: edit guardrails.toml, and add a must-block and a benign near-miss case to scripts/redteam.py).
  2. Run python3 scripts/run_gauntlet.py — every CI gate locally, report set under reports/.
  3. Open a PR; governance-ci.yml enforces 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.

About

A Claude Code plugin that governs your agent with deterministic, hook-level controls

Topics

Resources

License

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages