Signed Audit Trails for Claude Code Tool Calls
Unverified●30/40Claude Code◐PartialHas SKILL.md but declares no allowed-tools — Claude Code will ask for permission each time
Cursor·UnknownWe have not crawled the repo tree, so we will not guess
Codex·UnknownWe have not crawled the repo tree, so we will not guess
Gemini CLI·UnknownThe spec defines no detection rule for Gemini
Copilot·UnknownWe have not crawled the repo tree, so we will not guess
npx agentalley add signed-audit-trails-recipeWho is stuck, and on what
Step-by-step cookbook for setting up cryptographically signed audit trails on Claude Code tool calls. Use when explaining, evaluating, or demonstrating the pattern before committing to the protect-mcp runtime hooks. Covers Cedar policy, Ed25519 receipts, offline verification, tamper detection, CI/CD integration, and SLSA composition.
The whole source
Frontmatter — 2 properties
| name | signed-audit-trails-recipe |
|---|---|
| description | Step-by-step cookbook for setting up cryptographically signed audit trails on Claude Code tool calls. Use when explaining, evaluating, or demonstrating the pattern before committing to the protect-mcp runtime hooks. Covers Cedar policy, Ed25519 receipts, offline verification, tamper detection, CI/CD integration, and SLSA composition. |
| 1 | --- |
| 2 | name: signed-audit-trails-recipe |
| 3 | description: Step-by-step cookbook for setting up cryptographically signed audit trails on Claude Code tool calls. Use when explaining, evaluating, or demonstrating the pattern before committing to the protect-mcp runtime hooks. Covers Cedar policy, Ed25519 receipts, offline verification, tamper detection, CI/CD integration, and SLSA composition. |
| 4 | ---A5 — No allowed-tools declared — no way to tell what this skill may touch |
| 5 | |
| 6 | # Signed Audit Trails for Claude Code Tool Calls |
| 7 | |
| 8 | Cookbook-style walkthrough for cryptographically signed receipts on every |
| 9 | Claude Code tool call. This is the teaching skill. For the runtime |
| 10 | implementation, install the [`protect-mcp`](../../protect-mcp/) plugin. |
| 11 | |
| 12 | ## What this gives you |
| 13 | |
| 14 | Every tool call (`Bash`, `Edit`, `Write`, `WebFetch`) is:A4 — This skill pulls in web or user content but never says to treat that content as data. A signal, not proof. |
| 15 | |
| 16 | 1. **Evaluated against a Cedar policy** before execution. If the policy denies |
| 17 | the call, the tool does not run. |
| 18 | 2. **Signed as an Ed25519 receipt** after execution. Receipts are |
| 19 | JCS-canonical, hash-chained, and verifiable offline by anyone with the |
| 20 | public key. |
| 21 | |
| 22 | An auditor, regulator, or counterparty can verify the full chain later with a |
| 23 | single CLI command (`npx @veritasacta/verify receipts/*.json`). No network |
| 24 | call, no vendor lookup, no trust in the operator. |
| 25 | |
| 26 | ## When to use the pattern |
| 27 | |
| 28 | - **Regulated environments** (finance, healthcare, critical infrastructure) |
| 29 | where you need tamper-evident evidence of agent behavior |
| 30 | - **CI/CD pipelines** where you want to prove that a policy gate held for |
| 31 | every automated build step |
| 32 | - **Multi-party collaboration** where a counterparty wants to verify your |
| 33 | agent's behavior without trusting your operator |
| 34 | - **Compliance contexts** (EU AI Act Article 12, SLSA provenance for |
| 35 | agent-built software) where standard logging is not sufficient |
| 36 | |
| 37 | ## Step 1: Install the hook configuration |
| 38 | |
| 39 | Create `.claude/settings.json` in your project root: |
| 40 | |
| 41 | ```json |
| 42 | { |
| 43 | "hooks": { |
| 44 | "PreToolUse": [ |
| 45 | { |
| 46 | "matcher": ".*", |
| 47 | "hook": { |
| 48 | "type": "command", |
| 49 | "command": "npx protect-mcp@latest evaluate --policy ./protect.cedar --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --fail-on-missing-policy false" |
| 50 | } |
| 51 | } |
| 52 | ], |
| 53 | "PostToolUse": [ |
| 54 | { |
| 55 | "matcher": ".*", |
| 56 | "hook": { |
| 57 | "type": "command", |
| 58 | "command": "npx protect-mcp@latest sign --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --output \"$TOOL_OUTPUT\" --receipts ./receipts/ --key ./protect-mcp.key" |
| 59 | } |
| 60 | } |
| 61 | ] |
| 62 | } |
| 63 | } |
| 64 | ``` |
| 65 | |
| 66 | The first run of `protect-mcp sign` generates `./protect-mcp.key` (Ed25519 |
| 67 | private key) if one does not exist. Commit the **public** key fingerprint |
| 68 | (visible in any receipt's `public_key` field); do not commit the private |
| 69 | key. |
| 70 | |
| 71 | Add the private key and receipt directory to `.gitignore`: |
| 72 | |
| 73 | ```bash |
| 74 | echo "./protect-mcp.key" >> .gitignore |
| 75 | echo "./receipts/" >> .gitignore |
| 76 | ``` |
| 77 | |
| 78 | ## Step 2: Write a Cedar policy |
| 79 | |
| 80 | Create `./protect.cedar`: |
| 81 | |
| 82 | ```cedar |
| 83 | // Allow all read-oriented tools by default. |
| 84 | permit ( |
| 85 | principal, |
| 86 | action in [Action::"Read", Action::"Glob", Action::"Grep", Action::"WebSearch"], |
| 87 | resource |
| 88 | ); |
| 89 | |
| 90 | // Allow Bash commands from a safe list only. |
| 91 | permit ( |
| 92 | principal, |
| 93 | action == Action::"Bash", |
| 94 | resource |
| 95 | ) when { |
| 96 | context.command_pattern in [ |
| 97 | "git", "npm", "pnpm", "yarn", "ls", "cat", "pwd", |
| 98 | "echo", "test", "node", "python", "make" |
| 99 | ] |
| 100 | }; |
| 101 | |
| 102 | // Explicit deny on destructive commands. Cedar deny is authoritative. |
| 103 | forbid ( |
| 104 | principal, |
| 105 | action == Action::"Bash", |
| 106 | resource |
| 107 | ) when { |
| 108 | context.command_pattern in ["rm -rf", "dd", "mkfs", "shred"] |
| 109 | }; |
| 110 | |
| 111 | // Restrict writes to the project directory. |
| 112 | permit ( |
| 113 | principal, |
| 114 | action in [Action::"Write", Action::"Edit"], |
| 115 | resource |
| 116 | ) when { |
| 117 | context.path_starts_with == "./" |
| 118 | }; |
| 119 | ``` |
| 120 | |
| 121 | Four rules: |
| 122 | |
| 123 | - Read-oriented tools always allowed |
| 124 | - `Bash` allowed for safe command patterns (`git`, `npm`, etc.) |
| 125 | - `Bash rm -rf` and similar destructive commands explicitly denied |
| 126 | - Writes allowed only within the project (`./` prefix) |
| 127 | |
| 128 | Cedar `forbid` rules take precedence over `permit` rules, so destructive |
| 129 | commands cannot be bypassed by a later permissive rule. |
| 130 | |
| 131 | ## Step 3: Use Claude Code normally |
| 132 | |
| 133 | Start Claude Code. Every tool call goes through both hooks: |
| 134 | |
| 135 | ``` |
| 136 | You: Please read the README and summarize it. |
| 137 | |
| 138 | Claude: I will read README.md. |
| 139 | [PreToolUse: Read ./README.md -> allow] |
| 140 | [Tool: Read executes] |
| 141 | [PostToolUse: receipt rcpt-a8f3c9d2 signed to ./receipts/] |
| 142 | |
| 143 | ... summary of README ... |
| 144 | ``` |
| 145 | |
| 146 | A session of 20 tool calls produces 20 receipts, each hash-chained to its |
| 147 | predecessor. |
| 148 | |
| 149 | ## Step 4: Inspect a receipt |
| 150 | |
| 151 | ```bash |
| 152 | cat ./receipts/$(ls -t ./receipts/ | head -1) |
| 153 | ``` |
| 154 | |
| 155 | ```json |
| 156 | { |
| 157 | "receipt_id": "rcpt-a8f3c9d2", |
| 158 | "receipt_version": "1.0", |
| 159 | "issuer_id": "claude-code-protect-mcp", |
| 160 | "event_time": "2026-04-17T12:34:56.123Z", |
| 161 | "tool_name": "Read", |
| 162 | "input_hash": "sha256:a3f8c9d2e1b7465f...", |
| 163 | "decision": "allow", |
| 164 | "policy_id": "protect.cedar", |
| 165 | "policy_digest": "sha256:b7e2f4a6c8d0e1f3...", |
| 166 | "parent_receipt_id": "rcpt-3d1ab7c2", |
| 167 | "public_key": "4437ca56815c0516...", |
| 168 | "signature": "4cde814b7889e987..." |
| 169 | } |
| 170 | ``` |
| 171 | |
| 172 | Every field except `signature` and `public_key` is covered by the Ed25519 |
| 173 | signature. Modifying any field after signing invalidates the signature. |
| 174 | |
| 175 | ## Step 5: Verify the receipt chain |
| 176 | |
| 177 | ```bash |
| 178 | npx @veritasacta/verify ./receipts/*.json |
| 179 | ``` |
| 180 | |
| 181 | Exit codes: |
| 182 | |
| 183 | | Code | Meaning | |
| 184 | |------|---------| |
| 185 | | `0` | All receipts verified; chain intact | |
| 186 | | `1` | A receipt failed signature verification (tampered, or wrong key) | |
| 187 | | `2` | A receipt was malformed | |
| 188 | |
| 189 | ## Step 6: Demonstrate tamper detection |
| 190 | |
| 191 | Modify any receipt's `decision` field from `allow` to `deny`: |
| 192 | |
| 193 | ```bash |
| 194 | python3 -c " |
| 195 | import json, os |
| 196 | path = './receipts/' + sorted(os.listdir('./receipts'))[-1] |
| 197 | r = json.loads(open(path).read()) |
| 198 | r['decision'] = 'deny' |
| 199 | open(path, 'w').write(json.dumps(r)) |
| 200 | " |
| 201 | |
| 202 | npx @veritasacta/verify ./receipts/*.json |
| 203 | ``` |
| 204 | |
| 205 | The verifier exits with code `1` and reports which receipt failed. The |
| 206 | Ed25519 signature no longer matches the JCS-canonical bytes of the |
| 207 | tampered payload. |
| 208 | |
| 209 | Restore the field and verification passes again. |
| 210 | |
| 211 | ## How the cryptography works |
| 212 | |
| 213 | Three invariants make receipts verifiable offline across any conformant |
| 214 | implementation: |
| 215 | |
| 216 | 1. **JCS canonicalization (RFC 8785)** before signing. Keys sorted, |
| 217 | whitespace minimized, strings NFC-normalized. Two independent |
| 218 | implementations produce byte-identical signing payloads for the same |
| 219 | receipt content. |
| 220 | 2. **Ed25519 signatures (RFC 8032)** over the canonical bytes. |
| 221 | Deterministic, fixed-size, no nonce dependency. |
| 222 | 3. **Hash chain linkage.** Each receipt's `parent_receipt_hash` is the |
| 223 | SHA-256 of the predecessor's canonical form. Insertions, deletions, and |
| 224 | reorderings break later receipts. |
| 225 | |
| 226 | For the formal wire format see |
| 227 | [draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/). |
| 228 | |
| 229 | ## Cross-implementation interop |
| 230 | |
| 231 | The receipt format has four independent implementations today: |
| 232 | |
| 233 | | Implementation | Language | Use case | |
| 234 | |----------------|----------|----------| |
| 235 | | [protect-mcp](https://www.npmjs.com/package/protect-mcp) | TypeScript | Claude Code, Cursor, MCP hosts | |
| 236 | | [protect-mcp-adk](https://pypi.org/project/protect-mcp-adk/) | Python | Google Agent Development Kit | |
| 237 | | [sb-runtime](https://github.com/ScopeBlind/sb-runtime) | Rust | OS-level sandbox (Landlock + seccomp) | |
| 238 | | APS governance hook | Python | CrewAI, LangChain | |
| 239 | |
| 240 | A receipt produced by any of them verifies against |
| 241 | [`@veritasacta/verify`](https://www.npmjs.com/package/@veritasacta/verify). |
| 242 | The auditor does not need to trust the operator's tooling choice: the format |
| 243 | is the contract. |
| 244 | |
| 245 | ## CI/CD integration |
| 246 | |
| 247 | Gate merges on receipt chain verification so no build lands with a broken |
| 248 | evidence chain: |
| 249 | |
| 250 | ```yaml |
| 251 | # .github/workflows/verify-receipts.yml |
| 252 | name: Verify Decision Receipts |
| 253 | on: [push, pull_request] |
| 254 | |
| 255 | jobs: |
| 256 | verify: |
| 257 | runs-on: ubuntu-latest |
| 258 | steps: |
| 259 | - uses: actions/checkout@v4 |
| 260 | - uses: actions/setup-node@v4 |
| 261 | with: { node-version: '20' } |
| 262 | - name: Run governed agent |
| 263 | run: python scripts/run_agent.py > receipts.jsonl |
| 264 | - name: Verify receipt chain |
| 265 | run: npx @veritasacta/verify receipts.jsonl |
| 266 | ``` |
| 267 | |
| 268 | Archive the receipts as an artifact so the chain survives beyond the job run: |
| 269 | |
| 270 | ```yaml |
| 271 | - name: Upload receipts |
| 272 | if: always() |
| 273 | uses: actions/upload-artifact@v4 |
| 274 | with: |
| 275 | name: decision-receipts |
| 276 | path: receipts/ |
| 277 | ``` |
| 278 | |
| 279 | ## Composition with SLSA provenance for agent-built software |
| 280 | |
| 281 | When Claude Code builds and releases software (running `npm install`, |
| 282 | `npm build`, `npm publish` as tool calls), the receipt chain is the |
| 283 | per-step build log. SLSA Provenance v1 has an extension point for this: the |
| 284 | `byproducts` field can reference the receipt chain alongside the build |
| 285 | attestation. |
| 286 | |
| 287 | The [agent-commit build type](https://refs.arewm.com/agent-commit/v0.2) |
| 288 | documents the pattern using the ResourceDescriptor shape: |
| 289 | |
| 290 | ```json |
| 291 | { |
| 292 | "name": "decision-receipts", |
| 293 | "digest": { "sha256": "..." }, |
| 294 | "uri": "oci://registry/org/build-xyz/receipts:sha256-...", |
| 295 | "annotations": { |
| 296 | "predicateType": "https://veritasacta.com/attestation/decision-receipt/v0.1", |
| 297 | "signerRole": "supervisor-hook" |
| 298 | } |
| 299 | } |
| 300 | ``` |
| 301 | |
| 302 | The SLSA provenance is signed by the builder identity; the receipt |
| 303 | attestation is signed by the supervisor-hook identity. Two trust domains, |
| 304 | cross-referenced at the byproduct layer. See |
| 305 | [slsa-framework/slsa#1594](https://github.com/slsa-framework/slsa/issues/1594) |
| 306 | for the composition discussion. |
| 307 | |
| 308 | ## Common pitfalls |
| 309 | |
| 310 | **Private key in version control.** The generated `./protect-mcp.key` must |
| 311 | not be committed. The examples above add it to `.gitignore`. If a key is |
| 312 | accidentally committed, rotate immediately (delete the key file and let the |
| 313 | hook regenerate on next run). |
| 314 | |
| 315 | **Hook command quoting.** The hooks receive `$TOOL_NAME` and `$TOOL_INPUT` |
| 316 | as environment variables. Keep the quoting `"$TOOL_INPUT"` so inputs with |
| 317 | spaces or special characters pass through intact. |
| 318 | |
| 319 | **Receipts directory in CI.** If Claude Code runs in CI, upload receipts as |
| 320 | an artifact at the end of the job or the chain is lost at job end. |
| 321 | |
| 322 | **Policy is missing.** The example `PreToolUse` hook uses |
| 323 | `--fail-on-missing-policy false` so an absent `./protect.cedar` does not |
| 324 | break Claude Code out of the box. Remove this flag in production so a |
| 325 | missing policy is treated as a hard failure. |
| 326 | |
| 327 | ## Related in this marketplace |
| 328 | |
| 329 | - [`protect-mcp`](../../protect-mcp/) — the runtime hook implementation |
| 330 | (use this plugin in production) |
| 331 | - [`review-agent-governance`](../../review-agent-governance/) — require |
| 332 | human approval before review-surface actions; composes with protect-mcp |
| 333 | |
| 334 | ## References |
| 335 | |
| 336 | - [`draft-farley-acta-signed-receipts`](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/) — IETF draft, receipt wire format |
| 337 | - [RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032) — Ed25519 |
| 338 | - [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785) — JCS |
| 339 | - [Cedar policy language](https://docs.cedarpolicy.com/) |
| 340 | - [protect-mcp on npm](https://www.npmjs.com/package/protect-mcp) |
| 341 | - [@veritasacta/verify on npm](https://www.npmjs.com/package/@veritasacta/verify) |
| 342 | - [in-toto/attestation#549](https://github.com/in-toto/attestation/pull/549) — Decision Receipt predicate proposal |
| 343 | - [agent-commit build type](https://refs.arewm.com/agent-commit/v0.2) — SLSA provenance for agent-produced commits |
| 344 | - [Microsoft Agent Governance Toolkit](https://github.com/microsoft/agent-governance-toolkit) (`examples/protect-mcp-governed/`) |
| 345 | - [AWS Cedar for Agents](https://github.com/cedar-policy/cedar-for-agents) |
| 346 |
Reviews
Installed this one?Write the first review and take the Trailblazer badge.
Alternatives
Block No Verify HookConfigure a PreToolUse hook to prevent AI agents from skipping git pre-commit hooks with --no-verify and other bypass flags. Use when setting up Claude Code projects that enforce commit quality gates.◐····●35/40Sast ConfigurationConfigure Static Application Security Testing (SAST) tools for automated vulnerability detection in application code. Use when setting up security scanning, implementing DevSecOps practices, or automating code vulnerability detection.◐····●32/40Binary Analysis PatternsMaster binary analysis patterns including disassembly, decompilation, control flow analysis, and code pattern recognition. Use when analyzing executables, understanding compiled code, or performing static analysis on binaries.◐◐◐◐◐●31/40Anti Reversing TechniquesUnderstand anti-reversing, obfuscation, and protection techniques encountered during software analysis. Use this skill when analyzing malware evasion techniques, when implementing anti-debugging protections for CTF challenges, when reverse engineering packed binaries, or when building security research tools that need to detect virtualized environments.◐◐◐◐◐●30/40