Configure Cedar policy enforcement and Ed25519 signed receipts for Claude Code tool calls. Use when setting up projects that need cryptographic audit trails, po
复制下面这句话,粘贴给 Claude Code、Codex、Cursor 等 AI 编程工具,它会读取安装说明并在你确认后完成安装。
请阅读 https://ai.atlankj.com/install/asset/gh-protect-mcp-setup-02647560be30 ,按照其中的说明把「protect-mcp-setup」安装到你(当前 AI 工具)中。执行前先告诉我将运行的命令和写入的位置,等我确认。
查看 AI 将读取的安装说明正在读取 GitHub 原文…
内容来自 GitHub 原始文件,由原作者维护。在 GitHub 查看
Cryptographic governance for every Claude Code tool call. Each invocation is evaluated against a Cedar policy and produces an Ed25519-signed receipt that anyone can verify offline.
Claude Code runs powerful tools: Bash, Edit, Write, WebFetch. By default
there is no audit trail, no policy enforcement, and no way to prove what was
decided after the fact. protect-mcp closes all three gaps:
npx @veritasacta/verify. No server, no account,
no trust in the operator.AI agents make decisions that affect money, safety, and rights. The Claude Code session log records what happened, but the log is:
For compliance contexts (finance, healthcare, regulated research), this is not sufficient. You need tamper-evident evidence that can be verified by third parties without trusting you.
Add protect-mcp to your Claude Code project:
# 1. Install the plugin (adds hooks + skill to your project)
claude plugin install wshobson/agents/protect-mcp
# 2. Create ./protect.cedar (see below). The plugin installs the hooks.
# 3. Create the signing key once (protect-mcp 0.7.4 sign does not create it).
# An existing key is never replaced. See references/receipt-format.md to rotate.
if [ ! -e ./protect-mcp.key ]; then
d=$(mktemp -d) && npx protect-mcp@0.7.4 init --dir "$d" && mv "$d/keys/gateway.json" ./protect-mcp.key
fi
echo "/protect-mcp.key" >> .gitignore
# 4. Use Claude Code normally. Every tool call is now policy-evaluated
# and produces a signed receipt in ./receipts/
Installing the plugin adds both hooks from hooks/hooks.json. Each hook runs a
script bundled with the plugin:
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hooks": [
{ "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/evaluate.sh" }
]
}
],
"PostToolUse": [
{
"matcher": ".*",
"hooks": [
{ "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/sign.sh" }
]
}
]
}
}
Claude Code passes the hook event to the command as JSON on stdin and does not
set TOOL_NAME or TOOL_INPUT variables. evaluate.sh reads tool_name and
tool_input from that payload and passes them to protect-mcp as flags; sign.sh reads
tool_name only, because the 0.7.4 signer records nothing else. Set
PROTECT_MCP_POLICY, PROTECT_MCP_RECEIPTS, and PROTECT_MCP_KEY to change the
default paths. When the policy file is missing, the PreToolUse hook prints a
warning to stderr and allows the call.
PreToolUse — Runs BEFORE the tool executes. Evaluates the tool call against
your Cedar policy file. If Cedar returns deny, the hook exits with code 2 and
Claude Code blocks the tool call entirely.
PostToolUse runs AFTER the tool completes. It signs a receipt that names
the tool and appends it to ./receipts/receipts.jsonl. protect-mcp 0.7.4 does
not record the tool input or output.
Create ./protect.cedar at the project root:
// Read-only tools: one rule can name several tools in `when`. Add WebFetch
// with your own URL rule.
permit (principal, action == Action::"MCP::Tool::call", resource) when {
resource == Tool::"Read" || resource == Tool::"Glob" || resource == Tool::"Grep"
};
// Safe commands only; git limited to read subcommands
permit (principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
context has input && context.input has command &&
(context.input.command like "git status*" || context.input.command like "git diff*" ||
context.input.command like "git log*" || context.input.command like "git show*" ||
context.input.command like "npm*" || context.input.command like "ls*" ||
context.input.command like "cat*" || context.input.command like "echo*" ||
context.input.command like "pwd*" || context.input.command like "test*")
};
// No chaining (`&` also denies `2>&1`), `$` expansion, redirection (`>` or
// `<`, which covers `<(`), file output (`git diff --output`), or rm -rf
forbid (principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
context has input && context.input has command &&
(context.input.command like "*;*" || context.input.command like "*&*" ||
context.input.command like "*|*" || context.input.command like "*$*" ||
context.input.command like "*`*" || context.input.command like "*>*" ||
context.input.command like "*<*" || context.input.command like "*\n*" ||
context.input.command like "*--output*" || context.input.command like "*rm -rf*")
};
// Writes only inside the project (paths are absolute), never via `..`
permit (principal, action == Action::"MCP::Tool::call", resource) when {
(resource == Tool::"Write" || resource == Tool::"Edit") &&
context has input && context.input has file_path &&
context.input.file_path like "/path/to/project/*"
};
forbid (principal, action == Action::"MCP::Tool::call", resource) when {
(resource == Tool::"Write" || resource == Tool::"Edit") &&
context has input && context.input has file_path &&
(context.input.file_path like "*/../*" || context.input.file_path like "*/..")
};
String matching is best-effort: like checks the raw string, not a
resolved path, and an npm* permit runs arbitrary code, so it is only as
safe as the project's scripts.
Verify every receipt against the public key in ./protect-mcp.key:
PUB=$(node -p 'JSON.parse(require("fs").readFileSync("./protect-mcp.key")).publicKey')
npx @veritasacta/verify@0.9.2 --replay-chain ./receipts/receipts.jsonl --key "$PUB"
# Exit 0 = every receipt verified
# Exit 1 = a receipt failed (tampered, wrong key, or malformed line)
# Exit 2 = the file could not be read
The plugin's slash commands do the same inside Claude Code. /verify-receipt
takes one receipt in its own file, e.g., from
tail -n 1 ./receipts/receipts.jsonl > receipt.json.
/verify-receipt receipt.json
/audit-chain --last 20
Each receipt is one line of ./receipts/receipts.jsonl. See
references/receipt-format.md for a sample.
signature--key| Before | After |
|---|---|
| "Trust me, the agent only read files" | Cryptographically provable: every Read logged and signed |
| "The log shows it happened" | The receipt proves it happened, and no one can edit it |
| "You'd have to audit our system" | Anyone can verify every receipt offline |
| "Logs might be different by now" | Ed25519 signatures lock the record at signing time |