Claude Code guardrails
Stop Claude Code from running rm -rf
Claude Code asks before most dangerous commands. It does not ask for all of them, the prompt is easy to click through at speed, and nothing keeps a record of what you approved. This page shows the hook that blocks the command outright, and the receipt it leaves behind.
Why the permission prompt is not enough
Claude Code's permission system is a good default and it is not a control. Three things break it in practice:
- Approval fatigue. A long agent session produces dozens of prompts. The twentieth one gets the same reflex "yes" as the first, and that is the one that matters.
- Permission modes. The moment anyone runs an agent in a mode that skips prompts, for a long refactor or in CI, the prompt is not there at all.
- No record. When something does go wrong, the transcript is a chat log on one machine. It is editable, deletable, and it proves nothing to anyone who was not in the room.
A guardrail that works has to decide before the tool runs, be independent of the model's cooperation, and leave evidence that survives the person who caused the incident.
Install
Two commands inside Claude Code:
/plugin marketplace add pofky/provenrail
/plugin install provenrail-guard@provenrail
Then once per project you want guarded:
uv tool install provenrail # or: pip install provenrail
pr quickstart # local sink, no account, nothing leaves your machine
pr guard install # arms destructive + secrets + production
No signup, no API key, no network call in the decision path. The verdict is computed locally before anything is sent anywhere, so a guardrail never waits on a server and never fails because one is down.
Prefer not to install a plugin? pr guard install writes the same hooks into the project's .claude/settings.json directly, preserving any hooks already there.
What is blocked
Three rule packs are armed by default, 31 rules across seven packs are available. A representative sample:
| Pack | Blocks | Example |
|---|---|---|
| destructive | Recursive and forced deletion, disk overwrites, history rewrites | rm -rf ./src, git push --force, dd of=/dev/ |
| destructive | Irreversible infrastructure teardown | terraform destroy, kubectl delete namespace |
| destructive | Schema and data loss in SQL | DROP TABLE, TRUNCATE, DELETE with no WHERE |
| secrets | API keys and tokens appearing in a tool call | an sk- key pasted into a command, a private key written to disk |
| secrets | Permission changes that expose files | chmod 777 |
| production | Anything pointed at a production host or database | a connection string containing prod, a deploy command |
Rules are declared in a .provenrail.json file in the repo, so the policy is reviewable in a pull request like any other code. Nothing is "suspicious by default": if a rule did not declare it, it is not flagged.
Why some things ask instead of deny
This is the part most guardrail tools get wrong. A tool that hard-blocks everything risky gets uninstalled by lunchtime, because half of what it blocks is the work.
Provenrail rules carry an effect. deny stops the call and tells the agent which rule fired. require_oversight turns the call into a Claude Code permission prompt instead, and records your answer as human oversight in the log. Reading .env, running a migration, deploying: these should involve a human, not be impossible.
The distinction matters after the fact too. "The agent was blocked" and "a human approved this" are different facts, and a log that flattens them into one is not evidence of anything.
The receipt
Every decision, allowed, denied, or escalated, is Ed25519 signed and hash-chained to the one before it. Export it:
pr guard receipt
pr verify guard-receipt.json
Change one byte of that file and the verifier exits non-zero and names the broken link. Two independent implementations, a Python CLI and a browser verifier, are held in lockstep by frozen conformance vectors, so verification does not depend on running our code or trusting our servers. You can watch it happen in your own browser on a real record and on a tampered one.
Limits, stated plainly
- A hook can only see calls that go through Claude Code's tool boundary. A process that never calls the hooks is not constrained and not recorded. Provenrail never claims completeness, here or anywhere.
- If Provenrail is not installed, the plugin's hook exits silently and nothing is blocked or recorded. It warns once a day rather than failing on every tool call.
pr guard statusalways tells you the truth about what is armed. - Per-session blast-radius limits count within a single hook process, so they do not accumulate across a whole session yet. Deny and oversight rules are unaffected.
Guardrails reduce the blast radius of a mistake. They are not a security boundary against an adversary with shell access, and nobody should sell them as one.
Questions
- Does this slow the agent down?
- The decision is a local pattern match with no network call, so it costs milliseconds. Recording is written to a local journal and shipped asynchronously.
- Does it work outside Claude Code?
- The same policy engine runs as a Python SDK, so it can wrap tool calls in any agent framework. The plugin is the Claude Code front end for it.
- Is it free?
- The SDK, the CLI, the verifier and this plugin are MIT licensed and free, including for commercial use. Paid plans cover the hosted append-only sink, which is what makes a log evidence rather than a file the accused controls.
- Does anything leave my machine?
- Not with
pr quickstart. That configures a local sink and no account. Sending records off-box is a separate, explicit step, and it is the step that makes them tamper-evident to a third party. - Can the agent turn the guardrail off?
- The hook runs outside the model's context and the model does not get a vote on the verdict. It can, like any process with write access, edit the settings file, and that edit is itself a tool call the guardrail sees.