diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 5099217..002f7ae 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -289,6 +289,17 @@ "url": "https://github.com/1Password/SCAM" }, "source": "./plugins/security-awareness" + }, + { + "name": "verifying-agent-receipts", + "version": "1.0.0", + "description": "Teaches agents to verify cryptographically signed decision receipts produced by agent governance tooling. Covers Ed25519 signature verification, hash-chain integrity, tamper diagnosis, and offline verification via @veritasacta/verify. Use when auditing agent actions, responding to compliance requests, or diagnosing integrity failures in signed audit logs.", + "author": { + "name": "Tom Farley", + "email": "tommy@scopeblind.com", + "url": "https://github.com/ScopeBlind/scopeblind-gateway" + }, + "source": "./plugins/verifying-agent-receipts" } ] } diff --git a/README.md b/README.md index efdf9a9..10318c7 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Everything here has been code-reviewed by Trail of Bits staff. We're sharing it | [ghidra-headless](plugins/ghidra-headless/) | Reverse engineer binaries using Ghidra's headless analyzer | | [scv-scan](plugins/scv-scan/) | Audit Solidity codebases for 36 smart contract vulnerability classes | | [security-awareness](plugins/security-awareness/) | Recognize and avoid phishing, credential theft, and social engineering during agent operation | +| [verifying-agent-receipts](plugins/verifying-agent-receipts/) | Verify Ed25519-signed decision receipts and hash-chained audit trails from agent governance tooling | | [wooyun-legacy](plugins/wooyun-legacy/) | Web vulnerability testing methodology from 88,636 real-world cases (WooYun 2010-2016) | ### Research diff --git a/plugins/verifying-agent-receipts/.claude-plugin/plugin.json b/plugins/verifying-agent-receipts/.claude-plugin/plugin.json new file mode 100644 index 0000000..56620f3 --- /dev/null +++ b/plugins/verifying-agent-receipts/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "verifying-agent-receipts", + "version": "1.0.0", + "description": "Teaches agents to verify cryptographically signed decision receipts produced by agent governance tooling. Covers Ed25519 signature verification, hash-chain integrity, tamper diagnosis, Cedar policy digest matching, and offline verification via @veritasacta/verify. Use when auditing agent actions, responding to compliance requests, or diagnosing integrity failures in signed audit logs.", + "author": { + "name": "Tom Farley", + "email": "tommy@scopeblind.com", + "url": "https://github.com/ScopeBlind/scopeblind-gateway" + } +} diff --git a/plugins/verifying-agent-receipts/LICENSE b/plugins/verifying-agent-receipts/LICENSE new file mode 100644 index 0000000..ff13085 --- /dev/null +++ b/plugins/verifying-agent-receipts/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Tom Farley / ScopeBlind Pty Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/verifying-agent-receipts/README.md b/plugins/verifying-agent-receipts/README.md new file mode 100644 index 0000000..174498f --- /dev/null +++ b/plugins/verifying-agent-receipts/README.md @@ -0,0 +1,33 @@ +# Verifying Agent Receipts + +Teaches agents to verify cryptographically signed decision receipts produced by agent governance tooling. Covers Ed25519 signature verification, hash-chain integrity, tamper diagnosis, Cedar policy digest matching, and offline verification via `@veritasacta/verify`. + +## Install + +``` +/plugin install trailofbits/skills-curated/plugins/verifying-agent-receipts +``` + +## What This Skill Does + +When Claude encounters a signed receipt — a JSON file produced by `protect-mcp`, Microsoft Agent Governance Toolkit, Cedar-enforced MCP gateways, or any other system following the [IETF Internet-Draft for signed decision receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/) — this skill teaches it to: + +1. **Verify the Ed25519 signature** using `@veritasacta/verify` (Apache-2.0, offline, no network) +2. **Walk the hash chain** — confirm `parent_receipt_id` links resolve correctly +3. **Diagnose failures precisely** — signature mismatch vs. chain break vs. malformed receipt +4. **Resist common shortcuts** — the Rationalizations to Reject section catches the mistakes auditors make under time pressure + +## Standards This Skill Works With + +- **Ed25519** — [RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032) +- **JCS** — [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785) +- **Cedar** — [AWS's open authorization engine](https://www.cedarpolicy.com/) +- **IETF draft-farley-acta-signed-receipts** — signed decision receipt wire format + +## What This Skill Does NOT Do + +This is a **guidance-only skill**. It does not ship hooks, run background services, or make network calls from Claude Code. It teaches Claude how to reason about signed receipts and how to drive the external `@veritasacta/verify` CLI when the user chooses to install it. Runtime enforcement (policy evaluation before tool execution, signing after) is a separate tool — [`protect-mcp`](https://www.npmjs.com/package/protect-mcp) — that users install independently. + +## License + +MIT. See [LICENSE](./LICENSE). diff --git a/plugins/verifying-agent-receipts/skills/verifying-agent-receipts/SKILL.md b/plugins/verifying-agent-receipts/skills/verifying-agent-receipts/SKILL.md new file mode 100644 index 0000000..46c545e --- /dev/null +++ b/plugins/verifying-agent-receipts/skills/verifying-agent-receipts/SKILL.md @@ -0,0 +1,177 @@ +--- +name: verifying-agent-receipts +description: > + Verifies cryptographically signed decision receipts produced by agent + governance tooling (protect-mcp, Microsoft Agent Governance Toolkit, + Cedar-enforced MCP gateways). Covers Ed25519 signature checks, hash-chain + integrity, tamper diagnosis, and offline verification via + @veritasacta/verify. Use when auditing agent actions, responding to + compliance requests, or diagnosing integrity failures in signed audit logs. +allowed-tools: + - Read + - Glob + - Bash + - Grep +--- + +# Verifying Agent Receipts + +You are a receipt forensics specialist. Your job is to determine whether a signed decision receipt is authentic, what it attests to, and — when verification fails — which specific field changed and what that tells you about the attack. + +Assume the operator who handed you the receipts could be compromised. Your verification must be independent of their word. The Ed25519 signature is the evidence; everything else is narrative. + +## When to Use + +- Auditing an agent's actions after an incident (production deploy, financial transaction, data access) +- Responding to a compliance request that needs tamper-evident proof of agent behavior +- Diagnosing why `@veritasacta/verify` returned exit 1 or 2 +- Walking a hash-chained receipt directory to detect gaps or insertions +- Confirming that a receipt signer key matches the expected issuer +- Reviewing receipt output from `protect-mcp`, Microsoft Agent Governance Toolkit, or other IETF `draft-farley-acta-signed-receipts` implementations + +## When NOT to Use + +- Performing penetration tests or vulnerability scans — use offensive security tooling +- Writing the receipts themselves — use the governance tool (e.g. `protect-mcp`) that emits them +- Mapping receipts to specific compliance frameworks (SOC 2, ISO 27001) — pair with a compliance-specific skill after verification succeeds +- Verifying TLS certificates, JWTs, or code signatures — different signature systems with different formats + +## Rationalizations to Reject + +Auditors under time pressure reach for these shortcuts. Catch them. + +- **"The receipt looks structurally valid, so it's fine."** Structure is a prerequisite, not proof. A malformed receipt always fails verification; a well-formed receipt can still be tampered. Run `@veritasacta/verify` every time. +- **"Exit code 0 means everything is perfect."** Exit 0 means the signature verifies against the payload. It does NOT mean the receipt was issued by the *expected* signer. Always confirm the `public_key` fingerprint against a known-good source. +- **"The chain links look right by eyeball."** Eyeballing `parent_receipt_id` values works for the first few receipts and misses the one insertion in the middle. Walk the chain programmatically. +- **"The receipt passed once, so later edits are fine."** Any field edit breaks the signature. If verification fails now, the receipt was tampered since it was signed — no exceptions. +- **"The operator says that receipt was regenerated, it's the same thing."** A regenerated receipt is a new receipt with a new `receipt_id` and a new signature. The old receipt is gone. This is not equivalent and the operator should not be saying it is. + +## Receipt Format (reference) + +A valid receipt following `draft-farley-acta-signed-receipts` has these fields: + +```json +{ + "receipt_id": "rec_", + "receipt_version": "1.0", + "issuer_id": "string", + "event_time": "ISO 8601 UTC", + "tool_name": "string", + "decision": "allow" | "deny", + "policy_id": "string", + "policy_digest": "sha256:", + "input_hash": "sha256:", + "parent_receipt_id": "rec_ | null", + "public_key": "", + "signature": "" +} +``` + +The signature covers the JCS-canonicalized form of all fields except `signature` itself, keyed by the `public_key`. + +## Verification Workflow + +### Step 1 — Single receipt + +```bash +npx @veritasacta/verify +``` + +Exit codes: + +| Exit | Meaning | What it tells you | +|------|---------|-------------------| +| 0 | Valid | The signature verifies against the payload signed by `public_key`. Check the key fingerprint next. | +| 1 | Tampered | The signature does not verify. A field was modified after signing. Identify which field. | +| 2 | Malformed | Structural error — missing fields, wrong types. Not a valid receipt. | + +### Step 2 — Confirm the signer + +Exit 0 is necessary but not sufficient. The signer might be valid but not *the signer you expected*. Extract and display: + +```bash +jq -r '.public_key' +``` + +Compare the first 16 hex characters against a known-good signer list. If the list is not available, say so explicitly. Do not assert authenticity without a trusted key source. + +### Step 3 — Walk the chain + +```bash +ls -1 ./receipts/*.json | sort | npx @veritasacta/verify --chain +``` + +For each non-genesis receipt, confirm that `parent_receipt_id` equals the previous receipt's `receipt_id`. Report: + +- Gaps (missing receipts) — suggests deletion or dropped writes +- Insertions (parent points to a receipt not adjacent in time) — suggests insertion attack +- Forks (two receipts point to the same parent) — suggests a replay or double-write + +### Step 4 — Diagnose failures precisely + +When exit is 1 (tampered), compare the receipt payload byte-for-byte against a known-good copy (backup, mirror, external witness). The altered field is the answer. Common tampering patterns: + +- `decision` flipped from "deny" to "allow" +- `event_time` rolled forward or backward +- `input_hash` zeroed out or replaced +- `policy_id` swapped to reference a permissive policy +- `parent_receipt_id` rewired to hide a deleted event + +When exit is 2 (malformed), run `jq` to identify which required field is missing or wrong-typed. Report specifically — "missing `policy_digest`" not "the receipt is broken." + +## How to Explain Results + +### For a valid single receipt + +``` +Verified ✓ +Receipt: rec_8f92a3b1 +Tool: Bash (decision: allow) +Signed: 2026-04-15T10:30:00Z +Signer: 4437ca56815c0516... (matches expected issuer) +Chain: parent rec_3d1ab7c2 links correctly +``` + +### For a tampered receipt + +``` +TAMPERED ✗ +Receipt: rec_8f92a3b1 +Failing: signature does not match canonical payload + +Compare this file against a known-good copy to find the altered field. +The chain is intact, so the attacker modified one receipt in place +(payload tampering) rather than restructuring the sequence. +``` + +### For a chain break + +``` +CHAIN BREAK at position #142 +Receipt: rec_7a3b9c1e +Claimed parent: rec_8d4f2e91 +Expected parent: rec_6b5c1a8d + +All signatures are individually valid. The structure is wrong. +This means insertion, deletion, or fork — compare directory contents +against an external witness to determine which. +``` + +## Anti-Patterns to Avoid + +- **Treating an exit 0 as final.** The signature only proves integrity. Authenticity still requires matching `public_key` against an expected issuer. +- **Editing a receipt to "fix" a chain break.** Any edit invalidates the signature. If you need a demonstration, produce a new signed receipt with the correct parent — never alter an existing one. +- **Summarizing content before verifying.** If the receipt is tampered, its content is not trustworthy. Verify first, summarize second. +- **Quoting unsigned fields as evidence.** If a field is not covered by the signature (for example, an operator-added comment), it cannot be relied on. Only signed fields count. + +## Installing @veritasacta/verify + +If the user does not have `@veritasacta/verify` installed, it runs via `npx` on first use and requires no configuration. The verifier is Apache-2.0 licensed, operates entirely offline (no network calls), and depends only on `@noble/ed25519` for the curve operations. + +## References + +- [IETF draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/) +- [RFC 8032 (Ed25519)](https://datatracker.ietf.org/doc/html/rfc8032) +- [RFC 8785 (JCS)](https://datatracker.ietf.org/doc/html/rfc8785) +- [@veritasacta/verify on npm](https://www.npmjs.com/package/@veritasacta/verify) +- [Cedar authorization engine](https://www.cedarpolicy.com/)