Traces d'audit signées pour les appels d'outils Claude Code
Walkthrough de style cookbook pour les reçus cryptographiquement signés sur chaque appel d'outil Claude Code. Ceci est la skill d'enseignement. Pour l'implémentation runtime, installez le plugin protect-mcp.
Ce que cela vous donne
Chaque appel d'outil (Bash, Edit, Write, WebFetch) est :
- Évalué par rapport à une politique Cedar avant son exécution. Si la politique refuse l'appel, l'outil ne s'exécute pas.
- Signé en tant que reçu Ed25519 après son exécution. Les reçus sont JCS-canoniques, chaînés en hash, et vérifiables hors ligne par quiconque dispose de la clé publique.
Un auditeur, un régulateur ou une contrepartie peut vérifier la chaîne complète ultérieurement avec une seule commande CLI (npx @veritasacta/verify receipts/*.json). Pas d'appel réseau, pas de recherche auprès du fournisseur, pas de confiance envers l'opérateur.
Quand utiliser ce pattern
- Environnements réglementés (finance, santé, infrastructure critique) où vous avez besoin de preuves inviolables du comportement de l'agent
- Pipelines CI/CD où vous voulez prouver qu'une barrière de politique a tenu pour chaque étape de build automatisée
- Collaboration multipartite où une contrepartie veut vérifier le comportement de votre agent sans faire confiance à votre opérateur
- Contextes de conformité (EU AI Act Article 12, SLSA provenance pour les logiciels construits par agent) où la journalisation standard n'est pas suffisante
Étape 1 : Installer la configuration du hook
Créez .claude/settings.json à la racine de votre projet :
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hook": {
"type": "command",
"command": "npx protect-mcp@latest evaluate --policy ./protect.cedar --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --fail-on-missing-policy false"
}
}
],
"PostToolUse": [
{
"matcher": ".*",
"hook": {
"type": "command",
"command": "npx protect-mcp@latest sign --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --output \"$TOOL_OUTPUT\" --receipts ./receipts/ --key ./protect-mcp.key"
}
}
]
}
}
La première exécution de protect-mcp sign génère ./protect-mcp.key (clé privée Ed25519) s'il n'en existe pas. Commitez l'empreinte de la clé publique (visible dans le champ public_key de tout reçu) ; ne commitez pas la clé privée.
Ajoutez la clé privée et le répertoire des reçus à .gitignore :
echo "./protect-mcp.key" >> .gitignore
echo "./receipts/" >> .gitignore
Étape 2 : Écrire une politique Cedar
Créez ./protect.cedar :
// Autoriser tous les outils orientés lecture par défaut.
permit (
principal,
action in [Action::"Read", Action::"Glob", Action::"Grep", Action::"WebSearch"],
resource
);
// Autoriser les commandes Bash uniquement à partir d'une liste sûre.
permit (
principal,
action == Action::"Bash",
resource
) when {
context.command_pattern in [
"git", "npm", "pnpm", "yarn", "ls", "cat", "pwd",
"echo", "test", "node", "python", "make"
]
};
// Refus explicite des commandes destructrices. Le refus Cedar est autoritaire.
forbid (
principal,
action == Action::"Bash",
resource
) when {
context.command_pattern in ["rm -rf", "dd", "mkfs", "shred"]
};
// Restreindre les écritures au répertoire du projet.
permit (
principal,
action in [Action::"Write", Action::"Edit"],
resource
) when {
context.path_starts_with == "./"
};
Quatre règles :
- Les outils orientés lecture toujours autorisés
Bashautorisé pour les motifs de commandes sûrs (git,npm, etc.)Bash rm -rfet commandes destructrices similaires explicitement refusées- Écritures autorisées uniquement dans le projet (préfixe
./)
Les règles Cedar forbid ont priorité sur les règles permit, donc les commandes destructrices ne peuvent pas être contournées par une règle permissive ultérieure.
Étape 3 : Utiliser Claude Code normalement
Démarrez Claude Code. Chaque appel d'outil passe par les deux hooks :
You: Please read the README and summarize it.
Claude: I will read README.md.
[PreToolUse: Read ./README.md -> allow]
[Tool: Read executes]
[PostToolUse: receipt rcpt-a8f3c9d2 signed to ./receipts/]
... summary of README ...
Une session de 20 appels d'outils produit 20 reçus, chacun chaîné en hash à son prédécesseur.
Étape 4 : Inspecter un reçu
cat ./receipts/$(ls -t ./receipts/ | head -1)
{
"receipt_id": "rcpt-a8f3c9d2",
"receipt_version": "1.0",
"issuer_id": "claude-code-protect-mcp",
"event_time": "2026-04-17T12:34:56.123Z",
"tool_name": "Read",
"input_hash": "sha256:a3f8c9d2e1b7465f...",
"decision": "allow",
"policy_id": "protect.cedar",
"policy_digest": "sha256:b7e2f4a6c8d0e1f3...",
"parent_receipt_id": "rcpt-3d1ab7c2",
"public_key": "4437ca56815c0516...",
"signature": "4cde814b7889e987..."
}
Chaque champ sauf signature et public_key est couvert par la signature Ed25519. Modifier un champ quelconque après la signature invalide la signature.
Étape 5 : Vérifier la chaîne de reçus
npx @veritasacta/verify ./receipts/*.json
Codes de sortie :
| Code | Signification |
|---|---|
0 |
Tous les reçus vérifiés ; chaîne intacte |
1 |
Un reçu a échoué la vérification de signature (altéré, ou mauvaise clé) |
2 |
Un reçu était mal formé |
Étape 6 : Démontrer la détection d'altération
Modifiez le champ decision de n'importe quel reçu de allow à deny :
python3 -c "
import json, os
path = './receipts/' + sorted(os.listdir('./receipts'))[-1]
r = json.loads(open(path).read())
r['decision'] = 'deny'
open(path, 'w').write(json.dumps(r))
"
npx @veritasacta/verify ./receipts/*.json
Le vérificateur se termine avec le code 1 et rapporte quel reçu a échoué. La signature Ed25519 ne correspond plus aux octets JCS-canoniques de la charge utile altérée.
Restaurez le champ et la vérification réussit à nouveau.
Comment fonctionne la cryptographie
Trois invariants rendent les reçus vérifiables hors ligne dans toute implémentation conforme :
- Canonicalisation JCS (RFC 8785) avant la signature. Clés triées, espaces blancs minimisés, chaînes normalisées en NFC. Deux implémentations indépendantes produisent des charges utiles de signature identiques en octets pour le même contenu de reçu.
- Signatures Ed25519 (RFC 8032) sur les octets canoniques. Déterministes, taille fixe, pas de dépendance au nonce.
- Chaînage des hashes. Le
parent_receipt_hashde chaque reçu est le SHA-256 de la forme canonique du prédécesseur. Les insertions, suppressions et réordonnancements invalident les reçus ultérieurs.
Pour le format de câble formel, voir draft-farley-acta-signed-receipts.
Interopérabilité inter-implémentations
Le format de reçu compte quatre implémentations indépendantes aujourd'hui :
| Implémentation | Langage | Cas d'usage |
|---|---|---|
| protect-mcp | TypeScript | Claude Code, Cursor, hôtes MCP |
| protect-mcp-adk | Python | Google Agent Development Kit |
| sb-runtime | Rust | Sandbox au niveau OS (Landlock + seccomp) |
| APS governance hook | Python | CrewAI, LangChain |
Un reçu produit par l'une d'elles se vérifie par rapport à @veritasacta/verify. L'auditeur n'a pas besoin de faire confiance au choix d'outillage de l'opérateur : le format est le contrat.
Intégration CI/CD
Limitez les fusions à la vérification de la chaîne de reçus afin qu'aucun build ne fasse surface avec une chaîne de preuves rompue :
# .github/workflows/verify-receipts.yml
name: Verify Decision Receipts
on: [push, pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- name: Run governed agent
run: python scripts/run_agent.py > receipts.jsonl
- name: Verify receipt chain
run: npx @veritasacta/verify receipts.jsonl
Archivez les reçus en tant qu'artefact afin que la chaîne persiste au-delà de l'exécution du job :
- name: Upload receipts
if: always()
uses: actions/upload-artifact@v4
with:
name: decision-receipts
path: receipts/
Composition avec la provenance SLSA pour les logiciels construits par agent
Quand Claude Code construit et publie des logiciels (en exécutant npm install, npm build, npm publish en tant qu'appels d'outil), la chaîne de reçus est le journal de build par étape. SLSA Provenance v1 a un point d'extension pour cela : le champ byproducts peut référencer la chaîne de reçus aux côtés de l'attestation de build.
Le agent-commit build type documente le pattern en utilisant la forme ResourceDescriptor :
{
"name": "decision-receipts",
"digest": { "sha256": "..." },
"uri": "oci://registry/org/build-xyz/receipts:sha256-...",
"annotations": {
"predicateType": "https://veritasacta.com/attestation/decision-receipt/v0.1",
"signerRole": "supervisor-hook"
}
}
La provenance SLSA est signée par l'identité du constructeur ; l'attestation de reçu est signée par l'identité supervisor-hook. Deux domaines de confiance, référencés croisés à la couche byproduct. Voir slsa-framework/slsa#1594 pour la discussion de composition.
Pièges courants
Clé privée en contrôle de version. La clé ./protect-mcp.key générée ne doit pas être commitée. Les exemples ci-dessus l'ajoutent à .gitignore. Si une clé est accidentellement commitée, effectuez une rotation immédiatement (supprimez le fichier de clé et laissez le hook le régénérer à la prochaine exécution).
Guillemets de la commande du hook. Les hooks reçoivent $TOOL_NAME et $TOOL_INPUT en tant que variables d'environnement. Conservez les guillemets "$TOOL_INPUT" afin que les entrées contenant des espaces ou des caractères spéciaux passent intactes.
Répertoire des reçus en CI. Si Claude Code s'exécute en CI, chargez les reçus en tant qu'artefact à la fin du job sinon la chaîne est perdue à la fin du job.
La politique est manquante. Le hook PreToolUse d'exemple utilise --fail-on-missing-policy false afin qu'un ./protect.cedar absent ne casse pas Claude Code de manière prête à l'emploi. Supprimez cet indicateur en production afin qu'une politique manquante soit traitée comme une défaillance critique.
Contenu connexe dans cette marketplace
protect-mcp— l'implémentation du hook runtime (utilisez ce plugin en production)review-agent-governance— exiger l'approbation humaine avant les actions de surface d'examen ; se compose avec protect-mcp
Références
draft-farley-acta-signed-receipts— brouillon IETF, format de câble de reçu- RFC 8032 — Ed25519
- RFC 8785 — JCS
- Langage de politique Cedar
- protect-mcp sur npm
- @veritasacta/verify sur npm
- in-toto/attestation#549 — Proposition du prédicat Decision Receipt
- Agent-commit build type — Provenance SLSA pour les commits produits par agent
- Microsoft Agent Governance Toolkit (
examples/protect-mcp-governed/) - AWS Cedar for Agents