Guarda il video della lezione: Proteggere gli agenti AI con ricevute crittografiche
(Il team di contenuti Microsoft aggiungerà il video della lezione e la miniatura dopo la fusione, seguendo il modello della lezione 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 per voli per cercare opzioni e prenota i posti per conto del cliente. Lo scorso trimestre, l’agente ha gestito 50.000 prenotazioni.
Oggi arriva un revisore. Fa una domanda semplice: “Mostrami cosa ha fatto il tuo agente.”
Gli 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 delle distribuzioni di agenti oggi si basa su:
Nessuno di questi può rispondere alla domanda del revisore senza che il revisore debba fidarsi di qualcuno (te, il tuo provider cloud, il tuo fornitore di database). Per uso interno, questa fiducia è spesso accettabile. Per carichi di lavoro regolamentati (finanza, sanità, qualsiasi cosa soggetta all’AI Act dell’UE), non lo è.
Le ricevute crittografiche risolvono questo problema rendendo ogni azione dell’agente indipendentemente verificabile. Il revisore non deve fidarsi di te. Ha bisogno solo della tua chiave pubblica e della stessa ricevuta.
Una ricevuta è un oggetto JSON che registra ciò che un agente ha fatto, firmato con una firma digitale.
flowchart LR
A[Agente invoca uno strumento] --> B[Costruisci il payload della ricevuta]
B --> C[Canonicalizza JSON RFC 8785]
C --> D[Hash SHA-256]
D --> E[Firma Ed25519]
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à fanno il lavoro:
La firma. La ricevuta è firmata dal gateway dell’agente usando una chiave privata Ed25519. Chiunque abbia la corrispondente chiave pubblica può verificare la firma offline. La manomissione di qualsiasi campo invalida la firma.
Codifica canonica. Prima di firmare, la ricevuta viene serializzata usando lo JSON Canonicalization Scheme (JCS, RFC 8785). Questo assicura che due implementazioni che producono la stessa ricevuta logica producano un output identico byte per byte. Senza la canonicalizzazione, diversi serializer JSON produrrebbero firme diverse per lo stesso contenuto.
Catena di hash. Il campo previous_receipt_hash collega ogni ricevuta a quella precedente. Rimuovere o riordinare una ricevuta rompe tutte le ricevute successive. Le manomissioni diventano visibili a livello di catena anche se le firme individuali vengono aggirate.
Insieme queste proprietà forniscono tre garanzie:
Non serve una libreria speciale per produrre una ricevuta. Le primitive crittografiche sono ampiamente disponibili e la logica è di poche decine di righe di Python.
Gli esercizi pratici in code_samples/18-signed-receipts.ipynb illustrano l’intero flusso. La versione riassunta:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # JSON canonico 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()}"
# Genera o carica una chiave di firma (in produzione, conserva in un portachiavi)
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, esegui hash, firma.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature
# Allegare un oggetto firma strutturato.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Questa è tutta la pipeline di firma. Gli esercizi nel notebook spiegano 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)
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
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 terzi.
Per vedere in azione il rilevamento della manomissione, il notebook illustra:
tool_args_hash.Questa è la dimostrazione pratica che le ricevute evidenziano la manomissione: qualsiasi modifica, anche minima, rompe la firma.
Una singola ricevuta firmata protegge un’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 ricevuta precedente. Per rimuovere silenziosamente la ricevuta 2, un attaccante dovrebbe:
previous_receipt_hash della ricevuta 3 (rompe la firma della ricevuta 3), OPPURESe la chiave privata è in un hardware key vault e pubblichi la chiave pubblica con ogni ricevuta, nessun attacco è fattibile senza essere scoperto.
Il notebook mostra:
previous_receipt_hash di ogni ricevuta corrisponda all’hash reale della ricevuta precedente.Così si produce una traccia di controllo che un revisore esterno può verificare senza dover fidarsi di te.
Questa è la sezione più importante di questa lezione. Le ricevute sono potenti ma il loro potere ha un limite.
Le ricevute dimostrano tre cose:
Le ricevute NON dimostrano:
policy_id sia stata realmente valutata, o che avrebbe permesso quell’azione se verificata. La ricevuta registra ciò che è stato affermato, non ciò che è stato effettivamente applicato.Questo confine è importante per due motivi:
Un errore comune è assumere che “abbiamo le ricevute” significhi “siamo sotto controllo.” Non è così. Le ricevute sono una base. Il controllo è il sistema che costruisci sopra.
Il codice Python in questa lezione è volutamente 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 controllate.
Usare una libreria di ricevute in produzione. Diversi progetti open-source implementano lo stesso schema con funzionalità aggiuntive (rotazione delle chiavi, verifica batch, distribuzione JWK Set, integrazione con motori di policy):
draft-farley-acta-signed-receipts) attualmente in fase di standardizzazione.protect-mcp (npm) e @veritasacta/verify (npm) forniscono un’implementazione Node per la firma di ricevute e la verifica offline, pensati per avvolgere qualunque server MCP con una traccia di controllo resistente alle manomissioni.La scelta tra scrivere il proprio codice e usare una libreria rispecchia la scelta tra scrivere una libreria JWT da zero e usarne una testata: entrambe valide; la libreria fa risparmiare tempo e riduce la superficie di audit; la scrittura da zero ti costringe a comprendere ogni primitiva. Questa lezione insegna la strada 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 ha solo la chiave pubblica. Può verificare la ricevuta offline?
2. Un attaccante modifica il campo policy_id di una ricevuta per affermare che era governata 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 degli argomenti e risultati grezzi?
4. Il campo previous_receipt_hash collega ogni ricevuta alla sua predecessore. Se un attaccante elimina silenziosamente una ricevuta nel mezzo di una catena, cosa diventa invalido?
5. Una ricevuta verifica correttamente. Questo dimostra che l’azione dell’agente era 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 a tua scelta (ad esempio un ID richiesta per il tracciamento), aggiorna la logica di firma canonica per includerlo, e conferma che la ricevuta passi la verifica. Poi modifica il campo dopo la firma e conferma che la verifica fallisca. Questo ti costringe a capire come ogni byte della codifica canonica contribuisca alla firma. Sfida avanzata 2: Calcola l’hash SHA-256 di due delle tue ricevute insieme (concatenando i loro byte canonici in un ordine deterministico) e integra il digest risultante come un nuovo campo su una terza ricevuta prima di firmarla. Verifica che tutte e tre le ricevute possano ancora essere convertite avanti e indietro senza problemi. Hai appena costruito una prova di inclusione in un solo passaggio: chiunque possieda la terza ricevuta può dimostrare che le prime due esistevano al momento della sua firma, senza dover rivelare il loro contenuto. Questo è il pattern che le ricevute a divulgazione selettiva utilizzano su larga scala (impegni Merkle, RFC 6962).
Le ricevute crittografiche danno agli agenti AI una traccia di controllo che è:
Non sono un sostituto per la validazione degli input, l’applicazione di policy o l’infrastruttura di identità. Sono una base per questi livelli. Quando distribuisci agenti in carichi di lavoro regolamentati, flussi di lavoro multi-organizzazione o in qualsiasi contesto in cui non si possa presupporre che un revisore futuro ti sia affidabile, le ricevute sono il modo per 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 fosse vero o giusto. Mantieni saldamente questa distinzione. È la differenza tra un sistema di provenienza onesto e uno fuorviante.
Quando sarai pronto a passare da questa lezione a distribuire agenti firmati con ricevute in un ambiente reale:
https://your-org.example.com/.well-known/agent-keys.json.Iscriviti al Microsoft Foundry Discord per incontrare altri studenti, partecipare a ore di ufficio e ricevere risposte alle tue domande sugli Agenti AI.
Questa lezione copre la firma di una singola ricevuta e le sequenze concatenate tramite hash. Gli stessi primitivi si combinano in diversi pattern più avanzati che potresti incontrare man mano che matura la tua postura di governance:
authorization_*) e metà post-esecuzione (result_*) con firme indipendenti, utile quando la decisione di autorizzazione e il risultato osservato sono prodotti da attori o tempi differenti. Questo si sovrappone al formato ricevuta insegnato in questa lezione.result_hash. I payload reali sono spesso più ricchi di un singolo risultato di chiamata strumento: ragionamenti pre-decisione (predizione modello, opzioni considerate, prove e loro completezza, posizione di rischio, catena di responsabilità, esito di una gate) possono vivere dentro al payload, sigillati da una singola ricevuta. Mantiene il formato della ricevuta minimale permettendo agli schemi di payload di evolvere dominio-per-dominio.signature.alg può contenere ML-DSA-65 (lo standard NIST per firme post-quantistiche) quando serve migrare. Prevedi un periodo di transizione in cui le ricevute sono firmate doppiamente.Costruire Agenti per l’Uso del Computer (CUA)
(Da determinare dagli amministratori del curriculum)
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.