Skill 163 · Signed Audit Trails Recipe
Subchapter 163.2
references/ci-cd.mdMarkdown2 KBView on GitHub
The signing key is gitignored, so a CI job needs it from a secret. Create an
environment named receipt-signing and limit its deployment branches to the
default branch. Store the contents of ./protect-mcp.key as that
environment’s secret , and store its value as the
repository variable . The hooks sign receipts with the
secret key while the agent runs, and the verify step checks them with the
public key. Without the key, the receipts are unsigned and the verify step
exits 1.
PROTECT_MCP_KEYpublicKeyPROTECT_MCP_PUBLIC_KEYRun this job only on reviewed code, such as pushes to the default branch. Do not run it on pull requests, for two reasons:
scripts/run_agent.py after it writes the key to disk. The
branch could change the script to read the key and send it elsewhere.The environment’s branch limit is a second guard: if someone later adds a pull request trigger, jobs for other branches still cannot read the key.
# .github/workflows/verify-receipts.yml
name: Verify Decision Receipts
on:
push:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
environment: receipt-signing
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- name: Install signing key
env: { KEY: '${{ secrets.PROTECT_MCP_KEY }}' }
run: umask 077 && printf '%s' "$KEY" > protect-mcp.key
- name: Run governed agent
run: python scripts/run_agent.py
- name: Verify receipts
env: { PUB: '${{ vars.PROTECT_MCP_PUBLIC_KEY }}' }
run: npx @veritasacta/verify@0.9.2 --replay-chain receipts/receipts.jsonl --key "$PUB"
- name: Upload receipts
if: always()
uses: actions/upload-artifact@v4
with:
name: decision-receipts
path: receipts/The upload step keeps the receipts after the job ends, including when the verify step fails.