Regardez la vidéo de la leçon : Sécuriser les agents IA avec des reçus cryptographiques
(Vidéo de la leçon et vignette à ajouter par l’équipe de contenu Microsoft après fusion, selon le modèle des leçons 14 / 15.)
Cette leçon couvrira :
Après avoir complété cette leçon, vous saurez :
Imaginez que vous avez déployé un agent IA pour Contoso Travel. L’agent lit les demandes clients, appelle une API de vols pour rechercher des options, et réserve des sièges pour le client. Le trimestre dernier, l’agent a traité 50 000 réservations.
Aujourd’hui un auditeur arrive. Il pose une question simple : « Montrez-moi ce que votre agent a fait. »
Vous lui remettez vos fichiers journaux. L’auditeur les examine et pose une question plus difficile : « Comment puis-je savoir que ces journaux n’ont pas été modifiés ? »
C’est le problème de la piste d’audit. La plupart des déploiements d’agents reposent aujourd’hui sur :
Aucun de ces systèmes ne peut répondre à la question de l’auditeur sans que celui-ci doive faire confiance à quelqu’un (vous, votre fournisseur cloud, votre fournisseur de base de données). Pour un usage interne, cette confiance est souvent acceptable. Pour des charges réglementées (finances, santé, tout ce qui est soumis à la loi européenne sur l’IA), ce ne l’est pas.
Les reçus cryptographiques résolvent cela en rendant chaque action d’agent vérifiable indépendamment. L’auditeur n’a pas besoin de vous faire confiance. Il n’a besoin que de votre clé publique et du reçu lui-même.
Un reçu est un objet JSON qui enregistre ce qu’un agent a fait, signé avec une signature numérique.
flowchart LR
A[Agent invoque un outil] --> B[Construire la charge utile du reçu]
B --> C[Canonicaliser JSON RFC 8785]
C --> D[Hachage SHA-256]
D --> E[Signature Ed25519]
E --> F[Reçu avec signature]
F --> G[Auditeur vérifie hors ligne]
G --> H{Signature valide ?}
H -- oui --> I[Preuve infalsifiable]
H -- non --> J[Reçu rejeté]
Un reçu minimal ressemble à ceci :
{
"type": "agent.tool_call.v1",
"agent_id": "contoso-travel-bot",
"tool_name": "lookup_flights",
"tool_args_hash": "sha256:a3f9c1...",
"result_hash": "sha256:7b2e1d...",
"policy_id": "contoso-travel-policy-v3",
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 47,
"previous_receipt_hash": "sha256:9d4e6a...",
"signature": {
"alg": "EdDSA",
"sig": "c5af83...",
"public_key": "8f3b2c..."
}
}
Trois propriétés font le travail :
La signature. Le reçu est signé par la passerelle de l’agent avec une clé privée Ed25519. Quiconque possède la clé publique correspondante peut vérifier la signature hors ligne. Toute falsification d’un champ invalide la signature.
Encodage canonique. Avant la signature, le reçu est sérialisé en utilisant le JSON Canonicalization Scheme (JCS, RFC 8785). Cela garantit que deux implémentations produisant le même reçu logique produisent une sortie identique en octets. Sans canonisation, différents sérialiseurs JSON produiraient des signatures différentes pour le même contenu.
Chaînage par hachage. Le champ previous_receipt_hash relie chaque reçu à celui qui le précède. Supprimer ou réordonner un reçu casse tous les reçus suivants. La falsification devient visible au niveau de la chaîne même si les signatures individuelles sont contournées.
Ces propriétés fournissent ensemble trois garanties :
Vous n’avez pas besoin d’une bibliothèque spéciale pour produire un reçu. Les primitives cryptographiques sont largement disponibles et la logique tient en quelques dizaines de lignes de Python.
Les exercices pratiques dans code_samples/18-signed-receipts.ipynb expliquent tout le processus. La version résumée :
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # JSON canonique RFC 8785
def b64url_nopad(data: bytes) -> str:
return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")
def sha256_canonical(obj) -> str:
"""SHA-256 of a Python object's JCS-canonical JSON form."""
return f"sha256:{hashlib.sha256(canonicalize(obj)).hexdigest()}"
# Générer ou charger une clé de signature (en production, stocker dans un coffre à clés)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Construire la charge utile du reçu (pas encore de signature)
tool_args = {"origin": "SYD", "destination": "LAX"}
tool_result = [{"flight": "QF11", "price": 1850, "stops": 0}]
payload = {
"type": "agent.tool_call.v1",
"agent_id": "contoso-travel-bot",
"tool_name": "lookup_flights",
"tool_args_hash": sha256_canonical(tool_args),
"result_hash": sha256_canonical(tool_result),
"policy_id": "contoso-travel-policy-v3",
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 0,
"previous_receipt_hash": None,
}
# Canoniser, hacher, signer.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature
# Attacher un objet de signature structuré.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Voici toute la chaîne de signature. Les exercices du notebook expliquent chaque étape.
La vérification est l’opération inverse :
import base64
import hashlib
from nacl import signing
from nacl.exceptions import BadSignatureError
from jcs import canonicalize
def b64url_decode(s: str) -> bytes:
padding = "=" * ((4 - len(s) % 4) % 4)
return base64.urlsafe_b64decode(s + padding)
def verify_receipt(receipt: dict) -> bool:
# La signature est un objet structuré : {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Reconstruire la charge utile qui a effectivement été signée (tout sauf la signature).
payload = {k: v for k, v in receipt.items() if k != "signature"}
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
try:
verify_key = signing.VerifyKey(b64url_decode(sig_obj["public_key"]))
verify_key.verify(message_hash, b64url_decode(sig_obj["sig"]))
return True
except BadSignatureError:
return False
Cette fonction prend un reçu et renvoie True si la signature est valide, False sinon. Pas d’appel réseau, pas de dépendance à un service, aucune confiance requise envers une tierce partie.
Pour voir la détection de la falsification en action, le notebook suit :
tool_args_hash.C’est la démonstration pratique que les reçus sont à preuve de falsification : toute modification, même minime, casse la signature.
Un reçu signé protège une action unique. Une chaîne de reçus protège une séquence.
flowchart LR
R0[Reçu 0<br/>genèse] --> R1[Reçu 1]
R1 --> R2[Reçu 2]
R2 --> R3[Reçu 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Chaque reçu enregistre le hachage du reçu qui le précède. Pour supprimer discrètement le reçu 2, un attaquant devrait soit :
previous_receipt_hash du reçu 3 (ce qui casse la signature du reçu 3), OUSi la clé privée est dans un coffre-fort matériel et que vous publiez la clé publique avec chaque reçu, aucune des deux attaques n’est réalisable sans détection.
Le notebook explique :
previous_receipt_hash correspond bien au hachage réel du reçu précédent.C’est ainsi que vous produisez une piste d’audit qu’un auditeur externe peut vérifier sans vous faire confiance.
C’est la section la plus importante de cette leçon. Les reçus sont puissants mais leur pouvoir est borné.
Les reçus prouvent trois choses :
Les reçus NE prouvent PAS :
policy_id a été effectivement évaluée, ou qu’elle aurait permis cette action si elle avait été vérifiée. Le reçu enregistre ce qui a été affirmé, pas ce qui a été appliqué.Cette limite est importante pour deux raisons :
Une erreur fréquente est de supposer que « nous avons des reçus » signifie « nous sommes gouvernés ». Ce n’est pas le cas. Les reçus sont une base. La gouvernance est le système que vous construisez par-dessus.
Le code Python de cette leçon est délibérément minimal pour que vous puissiez lire chaque ligne et comprendre précisément ce qui se passe. En production, vous avez deux options :
Construire directement sur les primitives cryptographiques. Les 50 lignes vues plus haut suffisent pour de nombreux cas d’usage. PyNaCl (Ed25519) et le paquet jcs (JSON canonique) sont des bibliothèques bien maintenues et auditées.
Utiliser une bibliothèque de reçus en production. Plusieurs projets open source implémentent le même modèle avec des fonctionnalités supplémentaires (rotation de clés, vérification par lots, distribution JWK Set, intégration avec des moteurs de politique) :
draft-farley-acta-signed-receipts) en cours de normalisation.protect-mcp (npm) et @veritasacta/verify (npm) fournissent une implémentation Node de signature et de vérification hors ligne des reçus, conçue pour encapsuler tout serveur MCP avec une piste d’audit à preuve de falsification.Le choix entre créer soi-même ou utiliser une bibliothèque est similaire au choix entre écrire sa propre bibliothèque JWT ou utiliser une bibliothèque testée : les deux sont raisonnables ; la bibliothèque économise du temps et réduit la surface d’audit ; l’approche from-scratch vous force à comprendre chaque primitive. Cette leçon enseigne la voie from-scratch afin que vous ayez les bases pour l’un ou l’autre choix.
Testez votre compréhension avant de passer à l’exercice pratique.
1. Un reçu est signé avec la clé privée Ed25519 de l’agent. L’auditeur n’a que la clé publique. L’auditeur peut-il vérifier le reçu hors ligne ?
2. Un attaquant modifie le champ policy_id d’un reçu pour dire qu’il était gouverné par une politique plus permissive. La signature portait sur la charge utile originale. Que se passe-t-il lors de la vérification ?
3. Pourquoi le reçu inclut-il un tool_args_hash et un result_hash plutôt que les arguments et résultats bruts ?
4. Le champ previous_receipt_hash relie chaque reçu à son prédécesseur. Si un attaquant supprime discrètement un reçu en milieu de chaîne, qu’est-ce qui devient invalide ?
5. Un reçu vérifie correctement. Cela prouve-t-il que l’action de l’agent était correcte, conforme, ou fondée ?
Ouvrez code_samples/18-signed-receipts.ipynb et complétez les quatre sections :
Défi supplémentaire 1 : étendez le schéma de reçu avec un champ supplémentaire de votre choix (par exemple, un identifiant de requête pour le traçage), mettez à jour la logique de signature canonique pour l’inclure, et confirmez que le reçu vérifie toujours correctement. Puis modifiez ce champ après signature et confirmez que la vérification échoue. Cela vous oblige à comprendre comment chaque octet de l’encodage canonique contribue à la signature. Défi avancé 2 : Hachez ensemble avec SHA-256 deux de vos reçus (concaténez leurs octets canoniques dans un ordre déterministe) et intégrez le digest résultant comme un nouveau champ dans un troisième reçu avant de le signer. Vérifiez que les trois reçus peuvent toujours être convertis d’un format à l’autre. Vous venez de créer une preuve d’inclusion en une étape : toute personne possédant le troisième reçu peut prouver que les deux premiers existaient au moment où il a été signé, sans avoir besoin de révéler leur contenu. C’est le modèle utilisé à grande échelle par les reçus à divulgation sélective (engagements de Merkle, RFC 6962).
Les reçus cryptographiques fournissent aux agents IA une piste d’audit qui est :
Ils ne remplacent pas la validation des entrées, l’application des politiques ou l’infrastructure d’identité. Ils sont une base pour ces couches. Lorsque vous déployez des agents dans des charges de travail réglementées, des flux de travail multi-organisationnels, ou dans tout contexte où un auditeur futur ne peut pas être supposé vous faire confiance, les reçus sont le moyen de rendre la piste d’audit honnête.
Le point le plus important : les reçus prouvent qui a dit quoi, quand. Ils ne prouvent pas que ce qui a été dit est vrai ou correct. Conservez bien cette distinction. C’est la différence entre un système de provenance honnête et un système trompeur.
Lorsque vous êtes prêt à passer de cette leçon au déploiement d’agents signant des reçus dans un environnement réel :
https://your-org.example.com/.well-known/agent-keys.json.Rejoignez le Discord Microsoft Foundry pour rencontrer d’autres apprenants, participer à des permanences et obtenir des réponses à vos questions sur les agents IA.
Cette leçon couvre la signature d’un seul reçu et les séquences en chaîne de hachage. Les mêmes primitives composent plusieurs modèles plus avancés que vous rencontrerez à mesure que votre posture de gouvernance progresse :
authorization_*) et post-exécution (result_*) avec des signatures indépendantes, utile quand la décision d’autorisation et le résultat observé sont produits par des acteurs différents ou à des moments différents. Cela s’ajoute de façon additive au format de reçu présenté dans cette leçon.result_hash. Dans le monde réel, les charges sont souvent plus riches qu’un simple résultat d’appel d’outil : raisonnement pré-décision (prédiction modèle, options considérées, preuves et leur exhaustivité, posture de risque, chaîne de responsabilité, résultat de contrôle) peuvent tous être inclus dans la charge, scellés par un seul reçu. Cela garde le format du reçu minimal tout en permettant aux schémas de la charge d’évoluer domaine par domaine.signature.alg peut porter ML-DSA-65 (le standard de signature post-quantique de NIST) lorsque vous devez migrer. Prévoyez une période de transition où les reçus sont doublement signés.Construction d’agents d’utilisation informatique (CUA)
(À déterminer par les responsables du programme)
Avertissement : Ce document a été traduit à l’aide du service de traduction automatique Co-op Translator. Bien que nous nous efforçions d’assurer l’exactitude, veuillez noter que les traductions automatisées peuvent contenir des erreurs ou des inexactitudes. Le document original dans sa langue native doit être considéré comme la source faisant autorité. Pour les informations critiques, il est recommandé de recourir à une traduction professionnelle réalisée par un humain. Nous ne saurions être tenus responsables des malentendus ou erreurs d’interprétation découlant de l’utilisation de cette traduction.