Skip to content

Commit 2fbec29

Browse files
committed
docs(governance-receipts): add guide for signed decision receipts on ABCA runs
Adds a new guide under docs/guides/ showing how to wire cryptographically signed decision receipts into the ABCA agent pipeline. Each autonomous agent run produces a tamper-evident, offline-verifiable chain of receipts that a PR reviewer or regulator can check without trusting the orchestrator. The guide covers: - Why receipts matter for regulated ABCA deployments (current audit trail lives in CloudWatch, operator-controlled) - The pattern: PreToolUse Cedar policy evaluation + PostToolUse Ed25519 signing, matching the existing hook architecture in the Claude Code runtime that ABCA agents typically embed - Setup steps including the required Dockerfile addition (Node.js 18+ for npx), an example Cedar policy pinned to the agent's isolated workspace, the .claude/settings.json hook block, and a Python snippet for archiving receipts to S3 and referencing them from the opened PR - Receipt format with the three cryptographic invariants (JCS canonicalization per RFC 8785, Ed25519 signatures per RFC 8032, hash chain linkage) - Why Cedar is a natural fit for ABCA (same engine used by CDK, IAM Access Analyzer, Verified Permissions) - Composition with SLSA provenance via ResourceDescriptor byproducts (tracking the agent-commit build type) - How an external reviewer with no AWS access verifies a run - Interop table showing four independent implementations of the receipt format - What the guide intentionally does not cover (policy authoring at scale, key management, transparency-log anchoring) No changes to ABCA code. Purely additive documentation so teams adopting ABCA in regulated environments have a clear path to cryptographic audit trails without having to design the pattern themselves. Refs: https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/ https://github.com/cedar-policy/cedar-for-agents slsa-framework/slsa#1594 https://refs.arewm.com/agent-commit/v0.2
1 parent 396a245 commit 2fbec29

1 file changed

Lines changed: 338 additions & 0 deletions

File tree

docs/guides/GOVERNANCE_RECEIPTS.md

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
# Signed Decision Receipts for ABCA Agent Runs
2+
3+
**Add cryptographically verifiable evidence of every tool call an ABCA agent makes, so PR reviewers and regulators can verify the agent's behavior without trusting the orchestrator.**
4+
5+
---
6+
7+
## Why this matters for ABCA
8+
9+
ABCA agents run autonomously in isolated cloud environments, clone repos,
10+
write code, run tests, and open pull requests. Each step is a tool call
11+
(`git clone`, `npm install`, `npm test`, shell commands, file edits). By
12+
default, the evidence that each step happened and was authorized lives in
13+
CloudWatch logs, which are operator-controlled and trust the orchestrator
14+
not to redact or edit entries after the fact.
15+
16+
For regulated environments (financial services, healthcare, public sector)
17+
or for teams that want to hand a PR to a regulator or counterparty, a
18+
stronger evidence model is needed: each policy decision, signed at the
19+
moment it is made, with a tamper-evident chain across the full agent run.
20+
21+
Signed decision receipts provide this. A receipt is a small JSON object,
22+
signed with Ed25519, JCS-canonicalized, and hash-chained to its
23+
predecessor. Anyone with the public key can verify the whole chain offline.
24+
25+
## What this adds to the default ABCA pipeline
26+
27+
The ABCA agent runs inside the Dockerfile in `agent/`. Three additions:
28+
29+
1. **PreToolUse hook**: before every tool call, evaluate the call against a
30+
Cedar policy. Cedar denial blocks the tool call.
31+
2. **PostToolUse hook**: after every tool call, sign an Ed25519 receipt
32+
describing the decision, the policy digest, the inputs, and a hash link
33+
to the previous receipt.
34+
3. **Receipt artifact**: the receipt directory (`/tmp/receipts/`) is
35+
uploaded as a build artifact alongside the PR, and the PR body includes
36+
a one-line verification instruction.
37+
38+
Result: a reviewer or auditor running `npx @veritasacta/verify
39+
./receipts/*.json` gets an offline proof that every step of the agent run
40+
was authorized under the declared policy, with exit code 0 for valid, 1 for
41+
tampered, or 2 for malformed.
42+
43+
## The pattern
44+
45+
```
46+
┌──────────────────────────────────────────────────────────────────────────┐
47+
│ ABCA Autonomous Agent Runtime │
48+
│ │
49+
│ ┌────────────────┐ │
50+
│ │ Orchestrator │ │
51+
│ └───────┬────────┘ │
52+
│ │ invokes │
53+
│ ▼ │
54+
│ ┌────────────────┐ PreToolUse hook ┌───────────────────────────┐ │
55+
│ │ Agent Runtime │ ───────────────────▶ │ Cedar Policy Evaluator │ │
56+
│ │ (Claude Code / │ (before each call) │ ./protect.cedar │ │
57+
│ │ Amazon Q / │ │ allow → continue │ │
58+
│ │ similar) │ │ deny → exit 2, block │ │
59+
│ └───────┬────────┘ └───────────────────────────┘ │
60+
│ │ post-execution │
61+
│ ▼ │
62+
│ ┌────────────────┐ PostToolUse hook ┌──────────────────────────┐ │
63+
│ │ Tool output │ ────────────────────▶ │ Ed25519 Receipt Signer │ │
64+
│ └────────────────┘ (after each call) │ JCS canonical + chain │ │
65+
│ │ /tmp/receipts/*.json │ │
66+
│ └──────────────────────────┘ │
67+
└──────────────────────────────────────────────────────────────────────────┘
68+
```
69+
70+
## Setup
71+
72+
### 1. Add protect-mcp to the agent container
73+
74+
In `agent/Dockerfile`, ensure Node.js 18+ is installed (for `npx`):
75+
76+
```dockerfile
77+
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
78+
&& apt-get install -y nodejs
79+
```
80+
81+
### 2. Ship a Cedar policy with the agent image
82+
83+
Create `agent/protect.cedar` with allow/deny rules appropriate to the agent's
84+
scope. Example for a coding agent that runs in an isolated environment:
85+
86+
```cedar
87+
// Allow read-oriented tools on source files.
88+
permit (
89+
principal,
90+
action in [Action::"Read", Action::"Glob", Action::"Grep"],
91+
resource
92+
);
93+
94+
// Allow the build/test commands the agent needs.
95+
permit (
96+
principal,
97+
action == Action::"Bash",
98+
resource
99+
) when {
100+
context.command_pattern in [
101+
"git", "npm", "pnpm", "yarn", "uv", "python",
102+
"pytest", "cargo", "go", "make"
103+
]
104+
};
105+
106+
// Deny destructive commands even in the isolated environment.
107+
forbid (
108+
principal,
109+
action == Action::"Bash",
110+
resource
111+
) when {
112+
context.command_pattern in ["rm -rf", "dd", "mkfs", "shred"]
113+
};
114+
115+
// Writes only to the agent's working directory.
116+
permit (
117+
principal,
118+
action in [Action::"Write", Action::"Edit"],
119+
resource
120+
) when {
121+
context.path_starts_with == "/workspace/"
122+
};
123+
```
124+
125+
Policy authoring tips:
126+
127+
- **`forbid` is authoritative.** Destructive rules cannot be bypassed by a
128+
later permissive rule. Always write `forbid` for genuinely dangerous
129+
patterns.
130+
- **Restrict writes by path prefix.** Pin the agent to its working directory
131+
so it cannot accidentally modify CI config or credentials.
132+
- **Allow-list commands, do not deny-list.** The `Bash` permit rule above
133+
lists exactly the commands the agent is allowed to run. Any unknown
134+
command (e.g., a prompt-injected `curl malicious-url`) falls through to
135+
an implicit deny.
136+
137+
### 3. Configure Claude Code hooks
138+
139+
If the ABCA agent uses Claude Code, drop `.claude/settings.json` into the
140+
working directory before invoking:
141+
142+
```json
143+
{
144+
"hooks": {
145+
"PreToolUse": [
146+
{
147+
"matcher": ".*",
148+
"hook": {
149+
"type": "command",
150+
"command": "npx protect-mcp@latest evaluate --policy /agent/protect.cedar --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --fail-on-missing-policy false"
151+
}
152+
}
153+
],
154+
"PostToolUse": [
155+
{
156+
"matcher": ".*",
157+
"hook": {
158+
"type": "command",
159+
"command": "npx protect-mcp@latest sign --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --output \"$TOOL_OUTPUT\" --receipts /tmp/receipts/ --key /agent/protect-mcp.key"
160+
}
161+
}
162+
]
163+
}
164+
}
165+
```
166+
167+
The signing key (`/agent/protect-mcp.key`) is generated on first run.
168+
Persist it to AWS Secrets Manager for long-lived agents, or regenerate per
169+
run for ephemeral ones. Keep the **public** key fingerprint (visible in
170+
every receipt's `public_key` field) alongside the agent definition for
171+
verifiers.
172+
173+
### 4. Upload receipts as a PR artifact
174+
175+
In the agent's PR-opening step (`agent/src/...`), after the agent finishes:
176+
177+
```python
178+
import shutil, subprocess
179+
from pathlib import Path
180+
181+
# Archive receipts alongside the PR
182+
receipts_dir = Path("/tmp/receipts")
183+
if receipts_dir.exists():
184+
tarball = Path("/workspace/decision-receipts.tar.gz")
185+
subprocess.run(
186+
["tar", "-czf", str(tarball), "-C", str(receipts_dir), "."],
187+
check=True,
188+
)
189+
190+
# Upload to S3 for long-term retention (bucket from environment)
191+
subprocess.run(
192+
["aws", "s3", "cp", str(tarball),
193+
f"s3://abca-receipts/{task_id}/decision-receipts.tar.gz"],
194+
check=True,
195+
)
196+
197+
# Reference from the PR body
198+
pr_body += "\n\n## Decision Receipts\n"
199+
pr_body += f"This PR was produced by an autonomous agent. Decision receipts "
200+
pr_body += f"for every tool call are archived at "
201+
pr_body += f"`s3://abca-receipts/{task_id}/decision-receipts.tar.gz`.\n\n"
202+
pr_body += "Verify offline:\n\n"
203+
pr_body += "```bash\n"
204+
pr_body += f"aws s3 cp s3://abca-receipts/{task_id}/decision-receipts.tar.gz - | tar xz\n"
205+
pr_body += "npx @veritasacta/verify receipts/*.json\n"
206+
pr_body += "```\n"
207+
```
208+
209+
Reviewers see a link in the PR body; any stakeholder can run two commands
210+
to confirm the chain is intact.
211+
212+
## Receipt format
213+
214+
A single receipt:
215+
216+
```json
217+
{
218+
"receipt_id": "rcpt-a8f3c9d2",
219+
"receipt_version": "1.0",
220+
"issuer_id": "abca-agent-protect-mcp",
221+
"event_time": "2026-04-17T12:34:56.123Z",
222+
"tool_name": "Bash",
223+
"input_hash": "sha256:a3f8c9d2e1b7465f...",
224+
"decision": "allow",
225+
"policy_id": "protect.cedar",
226+
"policy_digest": "sha256:b7e2f4a6c8d0e1f3...",
227+
"parent_receipt_id": "rcpt-3d1ab7c2",
228+
"public_key": "4437ca56815c0516...",
229+
"signature": "4cde814b7889e987..."
230+
}
231+
```
232+
233+
Three invariants make this verifiable offline across any conformant
234+
implementation:
235+
236+
- **JCS canonicalization (RFC 8785)** before signing
237+
- **Ed25519 signatures (RFC 8032)** over the canonical bytes
238+
- **Hash chain linkage** via `parent_receipt_id`
239+
240+
Full wire-format spec:
241+
[draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/).
242+
243+
## Why Cedar for ABCA specifically
244+
245+
ABCA already uses AWS CDK for infrastructure. Cedar is AWS's open
246+
authorization language (used in Amazon Verified Permissions, AWS IAM
247+
Access Analyzer, and the CDK policy constructs). Using Cedar for agent
248+
authorization means:
249+
250+
- Same policy engine your AWS infrastructure teams already know
251+
- WASM bindings available ([cedar-policy/cedar-for-agents](https://github.com/cedar-policy/cedar-for-agents))
252+
so policy evaluation does not require a Rust toolchain in the container
253+
- AWS IAM analyzer can audit the policy file for logical errors
254+
- Policy changes are diffable in Git alongside the rest of the IaC
255+
256+
## Composition with SLSA provenance
257+
258+
When an ABCA agent produces a PR, the commit is the subject of a SLSA
259+
Provenance v1 attestation. The receipt chain rides as a ResourceDescriptor
260+
byproduct in that provenance, following the
261+
[agent-commit build type](https://refs.arewm.com/agent-commit/v0.2):
262+
263+
```json
264+
{
265+
"name": "decision-receipts",
266+
"digest": { "sha256": "..." },
267+
"uri": "s3://abca-receipts/<task_id>/decision-receipts.tar.gz",
268+
"annotations": {
269+
"predicateType": "https://veritasacta.com/attestation/decision-receipt/v0.1",
270+
"signerRole": "supervisor-hook"
271+
}
272+
}
273+
```
274+
275+
The SLSA provenance (signed by ABCA orchestrator identity) references the
276+
receipt chain (signed by the agent's supervisor-hook identity). Two trust
277+
domains, cross-referenced at the byproduct layer. See
278+
[slsa-framework/slsa#1594](https://github.com/slsa-framework/slsa/issues/1594)
279+
for the composition discussion.
280+
281+
## Verifying a PR from outside AWS
282+
283+
A reviewer without AWS account access can still verify an ABCA agent's run
284+
if the receipts are published in a publicly readable location:
285+
286+
```bash
287+
# 1. Download the archive
288+
curl -sL https://example.com/abca-receipts/task-123.tar.gz | tar xz
289+
290+
# 2. Verify the chain offline
291+
npx @veritasacta/verify receipts/*.json
292+
# Exit 0 = chain valid, 1 = tampered, 2 = malformed
293+
294+
# 3. Inspect any specific receipt
295+
jq '.' receipts/rcpt-a8f3c9d2.json
296+
```
297+
298+
No AWS credentials, no ABCA runtime, no trust in the orchestrator required.
299+
300+
## Cross-implementation interoperability
301+
302+
The receipt format is implemented by four independent codebases today:
303+
304+
| Implementation | Language | Use case |
305+
|----------------|----------|----------|
306+
| [protect-mcp](https://www.npmjs.com/package/protect-mcp) | TypeScript | Claude Code, Cursor |
307+
| [protect-mcp-adk](https://pypi.org/project/protect-mcp-adk/) | Python | Google ADK |
308+
| [sb-runtime](https://github.com/ScopeBlind/sb-runtime) | Rust | OS-level sandbox |
309+
| APS governance hook | Python | CrewAI, LangChain |
310+
311+
A chain produced in any of them verifies with any conformant verifier. The
312+
format is the contract.
313+
314+
## What this guide does not cover
315+
316+
- **Policy authoring at scale.** A production ABCA deployment likely needs
317+
multiple policies (per environment, per task risk tier). Cedar supports
318+
policy composition with explicit precedence rules; start simple and
319+
iterate.
320+
- **Key management.** The example above generates a key per run. Production
321+
deployments should use AWS Secrets Manager, AWS KMS, or a hardware
322+
security module (CloudHSM). For the strongest guarantee, bind the signing
323+
key to an ATECC608B secure element outside the agent's trust boundary.
324+
- **Transparency log anchoring.** Receipts can be anchored in Sigstore
325+
Rekor for cross-org verification with inclusion proofs. See
326+
[sigstore/rekor#2798](https://github.com/sigstore/rekor/issues/2798).
327+
328+
## References
329+
330+
- [`draft-farley-acta-signed-receipts`](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/) — IETF draft, receipt wire format
331+
- [RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032) — Ed25519
332+
- [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785) — JCS
333+
- [Cedar policy language](https://docs.cedarpolicy.com/)
334+
- [cedar-policy/cedar-for-agents](https://github.com/cedar-policy/cedar-for-agents) — WASM bindings
335+
- [protect-mcp on npm](https://www.npmjs.com/package/protect-mcp)
336+
- [@veritasacta/verify on npm](https://www.npmjs.com/package/@veritasacta/verify)
337+
- [in-toto/attestation#549](https://github.com/in-toto/attestation/pull/549) — Decision Receipt predicate proposal
338+
- [agent-commit build type](https://refs.arewm.com/agent-commit/v0.2) — SLSA provenance for agent-produced commits

0 commit comments

Comments
 (0)