Guarda il video della lezione: Proteggere gli Agenti AI con Ricevute Crittografiche
(Video della lezione e miniatura da aggiungere dal team contenuti Microsoft post-fusione, in linea con il modello delle lezioni 14 / 15.)
Questa lezione tratterà:
Dopo aver completato questa lezione, saprai come:
Immagina di aver distribuito un agente AI per Contoso Travel. L’agente legge le richieste dei clienti, chiama un’API voli per cercare opzioni e prenota posti per conto del cliente. Nell’ultimo trimestre, l’agente ha gestito 50.000 prenotazioni.
Oggi arriva un revisore. Fa una domanda semplice: “Mostrami cosa ha fatto il tuo agente.”
Consegni i tuoi file di log. Il revisore li guarda e fa una domanda più difficile: “Come faccio a sapere che questi log non sono stati modificati?”
Questo è il problema della traccia di controllo. La maggior parte degli agenti oggi si basa su:
Nessuno di questi può rispondere alla domanda del revisore senza richiedere che il revisore si fidi di qualcuno (te, il tuo provider cloud, il tuo fornitore di database). Per usi interni, questa fiducia è spesso accettabile. Per carichi regolamentati (finanza, sanità, qualsiasi cosa soggetta all’AI Act UE), non lo è.
Le ricevute crittografiche risolvono questo problema rendendo ogni azione dell’agente verificabile indipendentemente. Il revisore non ha bisogno di fidarsi di te. Serve solo la tua chiave pubblica e la ricevuta stessa.
Una ricevuta è un oggetto JSON che registra ciò che un agente ha fatto, firmato con una firma digitale.
flowchart LR
A[L'agente invoca uno strumento] --> B[Costruisci il payload della ricevuta]
B --> C[Canonicalizza JSON RFC 8785]
C --> E[Firma Ed25519 sui byte canonici]
E --> F[Ricevuta con firma]
F --> G[Revisore verifica offline]
G --> H{Firma valida?}
H -- yes --> I[Prova a prova di manomissione]
H -- no --> J[Ricevuta rifiutata]
Una ricevuta minima appare così:
{
"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..."
}
}
Tre proprietà svolgono il lavoro:
La firma. La ricevuta è firmata dal gateway dell’agente usando una chiave privata Ed25519. Chiunque abbia la chiave pubblica corrispondente può verificare la firma offline. Manomettere un campo invalida la firma.
Codifica canonica. Prima della firma, la ricevuta è serializzata usando lo schema di canonizzazione JSON (JCS, RFC 8785). Questo assicura che due implementazioni che producono la stessa ricevuta logica producano un output identico bit a bit. Senza canonizzazione, diversi serializzatori JSON produrrebbero firme diverse per lo stesso contenuto.
Concatenamento hash. Il campo previous_receipt_hash collega ogni ricevuta a quella precedente. Rimuovere o riordinare una ricevuta interrompe ogni ricevuta successiva. La manomissione diventa visibile a livello di catena anche se singole firme vengono bypassate.
Queste proprietà insieme forniscono tre garanzie:
Non serve una libreria speciale per produrre una ricevuta. Le primitive crittografiche sono ampiamente disponibili e la logica è poche decine di righe di Python.
Gli esercizi pratici in code_samples/18-signed-receipts.ipynb illustrano tutto il flusso. La versione riassunta:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # RFC 8785 JSON canonico
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()}"
# Genera o carica una chiave di firma (in produzione, memorizzarla in un caveau per chiavi)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Costruisci il payload della ricevuta (ancora senza firma)
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,
}
# Canonicalizza e firma direttamente i byte JCS. PureEdDSA esegue hash internamente.
canonical_bytes = canonicalize(payload)
signature_bytes = signing_key.sign(canonical_bytes).signature
# Allegare un oggetto firma strutturato.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Questo è l’intero processo di firma. Gli esercizi nel notebook esaminano ogni passaggio.
La verifica è l’operazione inversa:
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 firma è un oggetto strutturato: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Ricostruisci il payload che è stato effettivamente firmato (tutto tranne la firma).
payload = {k: v for k, v in receipt.items() if k != "signature"}
canonical_bytes = canonicalize(payload)
try:
verify_key = signing.VerifyKey(b64url_decode(sig_obj["public_key"]))
verify_key.verify(canonical_bytes, b64url_decode(sig_obj["sig"]))
return True
except BadSignatureError:
return False
Questa funzione prende una ricevuta e restituisce True se la firma è valida, False altrimenti. Nessuna chiamata di rete, nessuna dipendenza da servizi, nessuna fiducia richiesta in terze parti.
Per vedere la rilevazione di manomissioni in azione, il notebook guida attraverso:
tool_args_hash.Questa è la dimostrazione pratica che le ricevute sono evidenti a manomissioni: qualsiasi modifica, per quanto piccola, rompe la firma.
Una singola ricevuta firmata protegge una azione. Una catena di ricevute protegge una sequenza.
flowchart LR
R0[Ricevuta 0<br/>genesi] --> R1[Ricevuta 1]
R1 --> R2[Ricevuta 2]
R2 --> R3[Ricevuta 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Ogni ricevuta registra l’hash della precedente. Per rimuovere silenziosamente la ricevuta 2, un attaccante dovrebbe o:
previous_receipt_hash della ricevuta 3 (rompe la firma della ricevuta 3), OPPURESe la chiave privata è in un key vault hardware e pubblichi la chiave pubblica con ogni ricevuta, nessun attacco è fattibile senza essere rilevato.
Il notebook illustra:
previous_receipt_hash di ogni ricevuta corrisponda all’hash effettivo della ricevuta precedente.Questo è come si produce una traccia di controllo che un revisore esterno può verificare senza doversi fidare di te.
Questa è la sezione più importante della lezione. Le ricevute sono potenti ma il loro potere è limitato.
Le ricevute dimostrano tre cose:
Le ricevute NON dimostrano:
policy_id sia stata effettivamente valutata, o che avrebbe permesso l’azione se controllata. La ricevuta registra ciò che è stato dichiarato, non ciò che è stato applicato.Questo confine è importante per due ragioni:
Un errore comune è assumere che “abbiamo ricevute” significhi “siamo governati.” Non è così. Le ricevute sono una base. La governance è il sistema che costruisci sopra.
Il punto 3 sopra merita la sua sezione: una ricevuta di azione dice “questa chiave ha firmato questo contenuto,” mai “un umano ha autorizzato questo.” Per azioni ad alto rischio (rimborsi, cancellazioni, trasferimenti bancari), i framework di governance richiedono sempre più quella dichiarazione mancante, ed è producibile con le stesse primitive già costruite in questa lezione.
Il notebook successivo code_samples/human-authorization-receipts.ipynb aggiunge un secondo tipo di ricevuta, human.approval.v1, con lo stesso formato envelope delle ricevute della lezione (un payload tipizzato firmato da Ed25519 sulle sue byte canonici JCS, con l’oggetto signature fuori dai byte firmati). Un firmatario nominato firma l’intera azione canonica e il suo digest prima dell’esecuzione; la ricevuta dell’azione dell’agente porta lo stesso digest di azione e un parent_approval_ref, l’receipt_hash dell’approvazione, la stessa convenzione di previous_receipt_hash nella catena costruita sopra. Una verify_chain controlla entrambi gli artefatti sotto registri di chiavi separati e bloccati (chiavi degli approvatori vs chiavi dell’agente), così il percorso del codice è condiviso ma le autorità mai.
La proprietà acquisita, espressa con cura: l’umano ha approvato questa esatta azione, e l’agente ha eseguito proprio quell’azione approvata. I casi di rifiuto del notebook sono ciò che rende reale la proprietà piuttosto che solo affermata:
Ogni fallimento rifiuta per motivi distinti, così un revisore leggendo un rifiuto può capire se l’autorità è scaduta o se l’azione eseguita è cambiata. La regola insegnata dal notebook: una approvazione firmata non è autorità da sola. L’autorità esiste solo se entrambe le ricevute vincolano ancora la stessa azione canonica al momento dell’esecuzione. La ricevuta di approvazione umana è una composizione didattica definita da questa lezione, non un tipo di ricevuta definito da draft-farley-acta-signed-receipts.
Il codice Python in questa lezione è intenzionalmente minimale così puoi leggere ogni riga e capire esattamente cosa succede. In produzione hai due opzioni:
Costruire direttamente sulle primitive crittografiche. Le 50 righe viste sopra sono sufficienti per molti casi d’uso. PyNaCl (Ed25519) e il pacchetto jcs (JSON canonico) sono librerie ben mantenute e sottoposte a audit.
Usare una libreria di ricevute per la produzione. Vari progetti open-source implementano lo stesso modello con funzionalità aggiuntive (rotazione chiavi, verifica batch, distribuzione di JWK Set, integrazione con motori di policy):
draft-farley-acta-signed-receipts, revisione 02). La ricevuta piatta didattica della lezione differisce dall’envelope {payload, signature} del draft e non è presentata come implementazione conforme. Il draft pubblica una suite di conformità condivisa (agent-governance-testvectors) per implementazioni orientate al suo formato wire.protect-mcp (npm) e @veritasacta/verify (npm) forniscono un’implementazione Node di firma ricevute e verifica offline, pensata per incapsulare qualsiasi server MCP con traccia di controllo evidente a manomissioni, inclusi flussi in attesa di co-firma in cui un’azione sospesa emette una ricevuta di approvazione vincolata al digest dell’azione (supportato da WebAuthn nel flusso desktop), lo stesso modello di ricevuta approvazione umana del notebook sopra.pip install nobulex) offre lo stesso schema di firma Ed25519 + JCS con integrazioni LangChain e CrewAI, inclusi vettori di test di convalida incrociata pubblicati e una mappatura di conformità contribuita tramite OWASP PR #2210.La decisione tra implementare da soli o usare una libreria riflette la scelta tra scrivere una propria libreria JWT o usarne una testata: entrambe ragionevoli; la libreria fa risparmiare tempo e riduce la superficie di audit; il metodo da zero ti costringe a capire ogni primitiva. Questa lezione insegna il percorso da zero così hai la base per entrambe le scelte.
Metti alla prova la tua comprensione prima di passare all’esercizio pratico.
1. Una ricevuta è firmata con la chiave privata Ed25519 dell’agente. Il revisore possiede solo la chiave pubblica. Il revisore può verificare la ricevuta offline?
2. Un attaccante modifica il campo policy_id di una ricevuta per affermare che fosse regolata da una policy più permissiva. La firma era sul payload originale. Cosa succede durante la verifica?
3. Perché la ricevuta include un tool_args_hash e un result_hash invece dei parametri grezzi e del risultato?
4. Il campo previous_receipt_hash collega ogni ricevuta al suo predecessore. Se un attaccante elimina silenziosamente una ricevuta a metà di una catena, cosa diventa non valido?
5. Una ricevuta viene verificata correttamente. Questo prova che l’azione dell’agente è stata corretta, valida, o conforme alla policy?
Apri code_samples/18-signed-receipts.ipynb e completa tutte e quattro le sezioni:
Sfida extra 1: estendi lo schema della ricevuta con un campo aggiuntivo di tua scelta (per esempio un ID richiesta per il tracciamento), aggiorna la logica di firma canonica per includerlo, e conferma che la ricevuta venga sempre verificata correttamente. Poi modifica il campo dopo la firma e conferma il fallimento della verifica. Questo ti costringe a capire come ogni byte della codifica canonica contribuisce alla firma.
Sfida extra 2: applica l’hash SHA-256 a due delle tue ricevute insieme (concatenando i loro byte canonici in un ordine deterministico) e incorpora il digest risultante come nuovo campo in una terza ricevuta prima di firmarla. Verifica che tutte e tre le ricevute vengano ancora verificate correttamente. Hai appena costruito una prova di inclusione a un livello: chiunque possieda la terza ricevuta può dimostrare che le prime due esistevano al momento della firma, senza rivelarne i contenuti. Questo è il modello usato dalle ricevute a divulgazione selettiva su larga scala (impegni Merkle, RFC 6962).
Le ricevute crittografiche forniscono agli agenti AI una traccia di audit che è:
Non sono un sostituto per la validazione degli input, l’applicazione delle policy, o l’infrastruttura di identità. Sono una base per questi livelli. Quando distribuisci agenti in carichi di lavoro regolamentati, flussi multi-organizzazione o qualsiasi contesto in cui un futuro auditor non può considerarti affidabile, le ricevute sono come rendere onesta la traccia di audit.
Il punto più importante: le ricevute provano chi ha detto cosa e quando. Non provano che ciò che è stato detto sia vero o corretto. Mantieni questa distinzione saldamente. È la differenza tra un sistema di provenienza onesto e uno fuorviante.
Quando sei pronto a passare da questa lezione a distribuzioni di agenti con firma di ricevuta in ambienti reali:
https://your-org.example.com/.well-known/agent-keys.json.Unisciti al Microsoft Foundry Discord per incontrare altri studenti, partecipare a office hours e ottenere risposte alle tue domande sugli AI Agents.
Questa lezione copre la firma di ricevute singole e sequenze hash-catenate. Le stesse primitive compongono vari schemi più avanzati che potresti incontrare man mano che la tua postura di governance matura:
authorization_*) e post-esecuzione (result_*) con firme indipendenti, utile quando la decisione di autorizzazione e il risultato osservato sono prodotti da attori diversi o in momenti diversi. Questo si compone additivamente sopra al formato di ricevuta insegnato in questa lezione.result_hash. I payload reali spesso sono più ricchi del solo risultato di una chiamata a uno strumento: ragionamenti pre-decisione (predizione modello, opzioni considerate, prove e loro completezza, postura di rischio, catena di responsabilità, esito gate) possono risiedere tutti nel payload, sigillati da una singola ricevuta. Questo mantiene il formato della ricevuta minimo facendoti evolvere gli schemi payload per dominio.signature.alg può incorporare ML-DSA-65 (lo standard di firma post-quantistica NIST) quando serve migrare. Pianifica un periodo di transizione dove le ricevute sono firmate doppiamente.Disclaimer: Questo documento è stato tradotto utilizzando il servizio di traduzione AI Co-op Translator. Sebbene ci impegniamo per garantire la precisione, si prega di notare che le traduzioni automatizzate possono contenere errori o imprecisioni. Il documento originale nella sua lingua nativa deve essere considerato la fonte autorevole. Per informazioni critiche, si raccomanda una traduzione professionale effettuata da un essere umano. Non siamo responsabili per eventuali malintesi o interpretazioni errate derivanti dall’uso di questa traduzione.