CI/CD workflow for the signed audit trail recipe
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 PROTECT_MCP_KEY, and store its publicKey value as the
repository variable PROTECT_MCP_PUBLIC_KEY. 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.
Run this job only on reviewed code, such as pushes to the default branch. Do not run it on pull requests, for two reasons:
- GitHub does not pass secrets to a workflow run for a pull request from a fork, so the key is empty and the verify step fails.
- On a pull request from a branch in the same repository, the job would run
that branch's
scripts/run_agent.pyafter 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.