signed-audit-trails-recipe
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSigned Audit Trails for Claude Code Tool Calls
Claude Code工具调用的签名审计追踪
Cookbook-style walkthrough for cryptographically signed receipts on every
Claude Code tool call. This is the teaching skill. For the runtime
implementation, install the plugin.
protect-mcp本指南以食谱式分步讲解如何为每一次Claude Code工具调用生成加密签名收据。这是教学用技能,如需运行时实现,请安装插件。
protect-mcpWhat this gives you
功能特性
Every tool call (, , , ) is:
BashEditWriteWebFetch- Evaluated against a Cedar policy before execution. If the policy denies the call, the tool does not run.
- Signed as an Ed25519 receipt after execution. Receipts are JCS-canonical, hash-chained, and verifiable offline by anyone with the public key.
An auditor, regulator, or counterparty can verify the full chain later with a
single CLI command (). No network
call, no vendor lookup, no trust in the operator.
npx @veritasacta/verify receipts/*.json每一次工具调用(、、、)都会:
BashEditWriteWebFetch- 执行前通过Cedar策略评估。如果策略拒绝该调用,工具将不会运行。
- 执行后生成Ed25519签名收据。收据采用JCS规范化格式、哈希链式结构,任何拥有公钥的人均可离线验证。
审计人员、监管机构或合作方后续只需通过一条CLI命令即可验证完整链:。无需网络调用、无需查询供应商、无需信任操作人员。
npx @veritasacta/verify receipts/*.jsonWhen to use the pattern
适用场景
- Regulated environments (finance, healthcare, critical infrastructure) where you need tamper-evident evidence of agent behavior
- CI/CD pipelines where you want to prove that a policy gate held for every automated build step
- Multi-party collaboration where a counterparty wants to verify your agent's behavior without trusting your operator
- Compliance contexts (EU AI Act Article 12, SLSA provenance for agent-built software) where standard logging is not sufficient
- 受监管环境(金融、医疗、关键基础设施):需要防篡改的代理行为证据
- CI/CD流水线:需要证明每个自动化构建步骤均通过策略校验
- 多方协作场景:合作方无需信任操作人员即可验证你的代理行为
- 合规场景(欧盟AI法案第12条、代理构建软件的SLSA溯源):标准日志无法满足要求
Step 1: Install the hook configuration
步骤1:安装钩子配置
Create in your project root:
.claude/settings.jsonjson
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hook": {
"type": "command",
"command": "npx protect-mcp@latest evaluate --policy ./protect.cedar --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --fail-on-missing-policy false"
}
}
],
"PostToolUse": [
{
"matcher": ".*",
"hook": {
"type": "command",
"command": "npx protect-mcp@latest sign --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --output \"$TOOL_OUTPUT\" --receipts ./receipts/ --key ./protect-mcp.key"
}
}
]
}
}The first run of generates (Ed25519
private key) if one does not exist. Commit the public key fingerprint
(visible in any receipt's field); do not commit the private
key.
protect-mcp sign./protect-mcp.keypublic_keyAdd the private key and receipt directory to :
.gitignorebash
echo "./protect-mcp.key" >> .gitignore
echo "./receipts/" >> .gitignore在项目根目录创建:
.claude/settings.jsonjson
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hook": {
"type": "command",
"command": "npx protect-mcp@latest evaluate --policy ./protect.cedar --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --fail-on-missing-policy false"
}
}
],
"PostToolUse": [
{
"matcher": ".*",
"hook": {
"type": "command",
"command": "npx protect-mcp@latest sign --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --output \"$TOOL_OUTPUT\" --receipts ./receipts/ --key ./protect-mcp.key"
}
}
]
}
}首次运行时,如果不存在(Ed25519私钥),会自动生成。提交公钥指纹(可在任意收据的字段中查看);请勿提交私钥。
protect-mcp sign./protect-mcp.keypublic_key将私钥和收据目录添加到:
.gitignorebash
echo "./protect-mcp.key" >> .gitignore
echo "./receipts/" >> .gitignoreStep 2: Write a Cedar policy
步骤2:编写Cedar策略
Create :
./protect.cedarcedar
// Allow all read-oriented tools by default.
permit (
principal,
action in [Action::"Read", Action::"Glob", Action::"Grep", Action::"WebSearch"],
resource
);
// Allow Bash commands from a safe list only.
permit (
principal,
action == Action::"Bash",
resource
) when {
context.command_pattern in [
"git", "npm", "pnpm", "yarn", "ls", "cat", "pwd",
"echo", "test", "node", "python", "make"
]
};
// Explicit deny on destructive commands. Cedar deny is authoritative.
forbid (
principal,
action == Action::"Bash",
resource
) when {
context.command_pattern in ["rm -rf", "dd", "mkfs", "shred"]
};
// Restrict writes to the project directory.
permit (
principal,
action in [Action::"Write", Action::"Edit"],
resource
) when {
context.path_starts_with == "./"
};Four rules:
- Read-oriented tools always allowed
- allowed for safe command patterns (
Bash,git, etc.)npm - and similar destructive commands explicitly denied
Bash rm -rf - Writes allowed only within the project (prefix)
./
Cedar rules take precedence over rules, so destructive
commands cannot be bypassed by a later permissive rule.
forbidpermit创建:
./protect.cedarcedar
// 默认允许所有读取类工具。
permit (
principal,
action in [Action::"Read", Action::"Glob", Action::"Grep", Action::"WebSearch"],
resource
);
// 仅允许安全列表中的Bash命令。
permit (
principal,
action == Action::"Bash",
resource
) when {
context.command_pattern in [
"git", "npm", "pnpm", "yarn", "ls", "cat", "pwd",
"echo", "test", "node", "python", "make"
]
};
// 明确禁止破坏性命令。Cedar的forbid规则具有权威性。
forbid (
principal,
action == Action::"Bash",
resource
) when {
context.command_pattern in ["rm -rf", "dd", "mkfs", "shred"]
};
// 限制写入操作至项目目录内。
permit (
principal,
action in [Action::"Write", Action::"Edit"],
resource
) when {
context.path_starts_with == "./"
};四条规则:
- 始终允许读取类工具
- 仅允许安全命令模式(、
git等)的npm调用Bash - 明确禁止等类似破坏性命令
Bash rm -rf - 仅允许在项目目录内(前缀)执行写入操作
./
Cedar的规则优先级高于规则,因此破坏性命令无法被后续宽松规则绕过。
forbidpermitStep 3: Use Claude Code normally
步骤3:正常使用Claude Code
Start Claude Code. Every tool call goes through both hooks:
You: Please read the README and summarize it.
Claude: I will read README.md.
[PreToolUse: Read ./README.md -> allow]
[Tool: Read executes]
[PostToolUse: receipt rcpt-a8f3c9d2 signed to ./receipts/]
... summary of README ...A session of 20 tool calls produces 20 receipts, each hash-chained to its
predecessor.
启动Claude Code。每一次工具调用都会经过两个钩子:
你:请阅读README并总结内容。
Claude:我将读取README.md。
[PreToolUse: 读取 ./README.md -> 允许]
[工具:Read执行]
[PostToolUse: 收据rcpt-a8f3c9d2已签名至./receipts/]
... README摘要 ...一个包含20次工具调用的会话会生成20份收据,每份收据均与前一份形成哈希链。
Step 4: Inspect a receipt
步骤4:查看收据
bash
cat ./receipts/$(ls -t ./receipts/ | head -1)json
{
"receipt_id": "rcpt-a8f3c9d2",
"receipt_version": "1.0",
"issuer_id": "claude-code-protect-mcp",
"event_time": "2026-04-17T12:34:56.123Z",
"tool_name": "Read",
"input_hash": "sha256:a3f8c9d2e1b7465f...",
"decision": "allow",
"policy_id": "protect.cedar",
"policy_digest": "sha256:b7e2f4a6c8d0e1f3...",
"parent_receipt_id": "rcpt-3d1ab7c2",
"public_key": "4437ca56815c0516...",
"signature": "4cde814b7889e987..."
}Every field except and is covered by the Ed25519
signature. Modifying any field after signing invalidates the signature.
signaturepublic_keybash
cat ./receipts/$(ls -t ./receipts/ | head -1)json
{
"receipt_id": "rcpt-a8f3c9d2",
"receipt_version": "1.0",
"issuer_id": "claude-code-protect-mcp",
"event_time": "2026-04-17T12:34:56.123Z",
"tool_name": "Read",
"input_hash": "sha256:a3f8c9d2e1b7465f...",
"decision": "allow",
"policy_id": "protect.cedar",
"policy_digest": "sha256:b7e2f4a6c8d0e1f3...",
"parent_receipt_id": "rcpt-3d1ab7c2",
"public_key": "4437ca56815c0516...",
"signature": "4cde814b7889e987..."
}除和外,所有字段均受Ed25519签名保护。签名后修改任何字段都会使签名失效。
signaturepublic_keyStep 5: Verify the receipt chain
步骤5:验证收据链
bash
npx @veritasacta/verify ./receipts/*.jsonExit codes:
| Code | Meaning |
|---|---|
| All receipts verified; chain intact |
| A receipt failed signature verification (tampered, or wrong key) |
| A receipt was malformed |
bash
npx @veritasacta/verify ./receipts/*.json退出码说明:
| 代码 | 含义 |
|---|---|
| 所有收据验证通过;链完整 |
| 某份收据签名验证失败(已篡改或密钥错误) |
| 某份收据格式错误 |
Step 6: Demonstrate tamper detection
步骤6:演示篡改检测
Modify any receipt's field from to :
decisionallowdenybash
python3 -c "
import json, os
path = './receipts/' + sorted(os.listdir('./receipts'))[-1]
r = json.loads(open(path).read())
r['decision'] = 'deny'
open(path, 'w').write(json.dumps(r))
"
npx @veritasacta/verify ./receipts/*.jsonThe verifier exits with code and reports which receipt failed. The
Ed25519 signature no longer matches the JCS-canonical bytes of the
tampered payload.
1Restore the field and verification passes again.
将任意收据的字段从修改为:
decisionallowdenybash
python3 -c "
import json, os
path = './receipts/' + sorted(os.listdir('./receipts'))[-1]
r = json.loads(open(path).read())
r['decision'] = 'deny'
open(path, 'w').write(json.dumps(r))
"
npx @veritasacta/verify ./receipts/*.json验证器会以代码退出,并报告哪份收据验证失败。Ed25519签名将不再与篡改后的JCS规范化字节内容匹配。
1恢复该字段后,验证将再次通过。
How the cryptography works
加密原理
Three invariants make receipts verifiable offline across any conformant
implementation:
- JCS canonicalization (RFC 8785) before signing. Keys sorted, whitespace minimized, strings NFC-normalized. Two independent implementations produce byte-identical signing payloads for the same receipt content.
- Ed25519 signatures (RFC 8032) over the canonical bytes. Deterministic, fixed-size, no nonce dependency.
- Hash chain linkage. Each receipt's is the SHA-256 of the predecessor's canonical form. Insertions, deletions, and reorderings break later receipts.
parent_receipt_hash
For the formal wire format see
draft-farley-acta-signed-receipts.
三个不变量确保收据可在任何合规实现中离线验证:
- 签名前采用JCS规范化(RFC 8785)。按键排序、最小化空白、字符串NFC规范化。两个独立实现针对相同收据内容会生成字节完全一致的签名载荷。
- 对规范化字节进行Ed25519签名(RFC 8032)。确定性、固定大小、无需随机数依赖。
- 哈希链关联。每份收据的是前一份收据规范化形式的SHA-256哈希值。插入、删除或重新排序收据都会破坏后续收据的链结构。
parent_receipt_hash
有关正式有线格式,请参阅draft-farley-acta-signed-receipts。
Cross-implementation interop
跨实现互操作性
The receipt format has four independent implementations today:
| Implementation | Language | Use case |
|---|---|---|
| protect-mcp | TypeScript | Claude Code, Cursor, MCP hosts |
| protect-mcp-adk | Python | Google Agent Development Kit |
| sb-runtime | Rust | OS-level sandbox (Landlock + seccomp) |
| APS governance hook | Python | CrewAI, LangChain |
A receipt produced by any of them verifies against
.
The auditor does not need to trust the operator's tooling choice: the format
is the contract.
@veritasacta/verify目前收据格式有四个独立实现:
| 实现 | 语言 | 适用场景 |
|---|---|---|
| protect-mcp | TypeScript | Claude Code、Cursor、MCP主机 |
| protect-mcp-adk | Python | Google Agent Development Kit |
| sb-runtime | Rust | 操作系统级沙箱(Landlock + seccomp) |
| APS治理钩子 | Python | CrewAI、LangChain |
任何实现生成的收据均可通过验证。审计人员无需信任操作人员的工具选择:格式就是契约。
@veritasacta/verifyCI/CD integration
CI/CD集成
Gate merges on receipt chain verification so no build lands with a broken
evidence chain:
yaml
undefined通过收据链验证来管控合并操作,确保没有构建会破坏证据链:
yaml
undefined.github/workflows/verify-receipts.yml
.github/workflows/verify-receipts.yml
name: Verify Decision Receipts
on: [push, pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- name: Run governed agent
run: python scripts/run_agent.py > receipts.jsonl
- name: Verify receipt chain
run: npx @veritasacta/verify receipts.jsonl
Archive the receipts as an artifact so the chain survives beyond the job run:
```yaml
- name: Upload receipts
if: always()
uses: actions/upload-artifact@v4
with:
name: decision-receipts
path: receipts/name: Verify Decision Receipts
on: [push, pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- name: Run governed agent
run: python scripts/run_agent.py > receipts.jsonl
- name: Verify receipt chain
run: npx @veritasacta/verify receipts.jsonl
将收据归档为工件,确保链在作业结束后仍然保留:
```yaml
- name: Upload receipts
if: always()
uses: actions/upload-artifact@v4
with:
name: decision-receipts
path: receipts/Composition with SLSA provenance for agent-built software
与代理构建软件的SLSA溯源组合
When Claude Code builds and releases software (running ,
, as tool calls), the receipt chain is the
per-step build log. SLSA Provenance v1 has an extension point for this: the
field can reference the receipt chain alongside the build
attestation.
npm installnpm buildnpm publishbyproductsThe agent-commit build type
documents the pattern using the ResourceDescriptor shape:
json
{
"name": "decision-receipts",
"digest": { "sha256": "..." },
"uri": "oci://registry/org/build-xyz/receipts:sha256-...",
"annotations": {
"predicateType": "https://veritasacta.com/attestation/decision-receipt/v0.1",
"signerRole": "supervisor-hook"
}
}The SLSA provenance is signed by the builder identity; the receipt
attestation is signed by the supervisor-hook identity. Two trust domains,
cross-referenced at the byproduct layer. See
slsa-framework/slsa#1594
for the composition discussion.
当Claude Code构建并发布软件(执行、、等工具调用)时,收据链就是分步构建日志。SLSA Provenance v1为此提供了扩展点:字段可在构建证明之外引用收据链。
npm installnpm buildnpm publishbyproductsagent-commit构建类型使用ResourceDescriptor格式记录了该模式:
json
{
"name": "decision-receipts",
"digest": { "sha256": "..." },
"uri": "oci://registry/org/build-xyz/receipts:sha256-...",
"annotations": {
"predicateType": "https://veritasacta.com/attestation/decision-receipt/v0.1",
"signerRole": "supervisor-hook"
}
}SLSA溯源由构建者身份签名;收据证明由supervisor-hook身份签名。两个信任域在副产品层交叉引用。有关组合讨论,请参阅slsa-framework/slsa#1594。
Common pitfalls
常见陷阱
Private key in version control. The generated must
not be committed. The examples above add it to . If a key is
accidentally committed, rotate immediately (delete the key file and let the
hook regenerate on next run).
./protect-mcp.key.gitignoreHook command quoting. The hooks receive and
as environment variables. Keep the quoting so inputs with
spaces or special characters pass through intact.
$TOOL_NAME$TOOL_INPUT"$TOOL_INPUT"Receipts directory in CI. If Claude Code runs in CI, upload receipts as
an artifact at the end of the job or the chain is lost at job end.
Policy is missing. The example hook uses
so an absent does not
break Claude Code out of the box. Remove this flag in production so a
missing policy is treated as a hard failure.
PreToolUse--fail-on-missing-policy false./protect.cedar私钥进入版本控制。生成的绝对不能提交。上述示例已将其添加到。如果密钥意外提交,请立即轮换(删除密钥文件,下次运行钩子时会重新生成)。
./protect-mcp.key.gitignore钩子命令引号。钩子会接收和作为环境变量。请保留的引号,以便包含空格或特殊字符的输入能完整传递。
$TOOL_NAME$TOOL_INPUT"$TOOL_INPUT"CI中的收据目录。如果Claude Code在CI中运行,请在作业结束时将收据上传为工件,否则链会在作业结束时丢失。
策略缺失。示例中的钩子使用,因此即使缺少也不会影响Claude Code的正常运行。生产环境中请移除该标志,将缺失策略视为严重错误。
PreToolUse--fail-on-missing-policy false./protect.cedarRelated in this marketplace
市场中相关资源
- — the runtime hook implementation (use this plugin in production)
protect-mcp - — require human approval before review-surface actions; composes with protect-mcp
review-agent-governance
- — 运行时钩子实现(生产环境请使用此插件)
protect-mcp - — 要求人工批准后才能执行审查相关操作;可与protect-mcp组合使用
review-agent-governance
References
参考资料
- — IETF draft, receipt wire format
draft-farley-acta-signed-receipts - RFC 8032 — Ed25519
- RFC 8785 — JCS
- Cedar policy language
- protect-mcp on npm
- @veritasacta/verify on npm
- in-toto/attestation#549 — Decision Receipt predicate proposal
- agent-commit build type — SLSA provenance for agent-produced commits
- Microsoft Agent Governance Toolkit ()
examples/protect-mcp-governed/ - AWS Cedar for Agents
- — IETF草案,收据有线格式
draft-farley-acta-signed-receipts - RFC 8032 — Ed25519
- RFC 8785 — JCS
- Cedar策略语言
- npm上的protect-mcp
- npm上的@veritasacta/verify
- in-toto/attestation#549 — 决策收据谓词提案
- agent-commit构建类型 — 代理生成提交的SLSA溯源
- Microsoft Agent Governance Toolkit()
examples/protect-mcp-governed/ - AWS Cedar for Agents