Guarda il video della lezione: Proteggere gli agenti AI con ricevute crittografiche
(Video della lezione e miniatura da aggiungere dal team contenuti Microsoft dopo la fusione, seguendo il modello delle lezioni 14 / 15.)
Questa lezione coprirà:
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 di voli per cercare opzioni e prenota posti per conto del cliente. Lo scorso trimestre, l’agente ha elaborato 50.000 prenotazioni.
Oggi arriva un revisore. Fa una domanda semplice: “Mostrami cosa ha fatto il tuo agente.”
Gli consegni i file di log. Il revisore li guarda e pone una domanda più difficile: “Come faccio a sapere che questi log non sono stati modificati?”
Questo è il problema della traccia di audit. La maggior parte delle implementazioni di agenti oggi si basa su:
Nessuno di questi può rispondere alla domanda del revisore senza farlo dipendere da qualcuno (te, il tuo provider cloud, il fornitore del database). Per uso interno, quella fiducia è spesso accettabile. Per workload regolamentati (finanza, sanità, qualsiasi cosa soggetta all’EU AI Act), non lo è.
Le ricevute crittografiche risolvono questo problema rendendo ogni azione dell’agente verificabile indipendentemente. Il revisore non deve fidarsi di te. Gli basta la tua chiave pubblica e la ricevuta stessa.
Una ricevuta è un oggetto JSON che registra cosa ha fatto un agente, firmato con una firma digitale.
flowchart LR
A[Agente invoca uno strumento] --> B[Costruisci 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 chiave pubblica corrispondente può verificarla offline. Manomettere un campo invalida la firma.
Codifica canonica. Prima della firma, 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 byte-identico. Senza la canonizzazione, diversi serializer JSON produrrebbero firme differenti per lo stesso contenuto.
Concatenamento tramite hash. Il campo previous_receipt_hash collega ogni ricevuta a quella precedente. Rimuovere o riorganizzare una ricevuta invalida tutte quelle successive. La manomissione diventa visibile a livello di catena anche se le singole firme venissero aggirate.
Queste proprietà insieme offrono 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 guidano 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, memorizzala in un caveau di 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, 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)),
},
}
Questo è l’intero flusso 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
# Ricostruire 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 in terze parti.
Per vedere come funziona il rilevamento di manomissioni, il notebook spiega:
tool_args_hash.Questa è la dimostrazione pratica che le ricevute evidenziano le manomissioni: qualsiasi modifica, per quanto piccola, invalida 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 o:
previous_receipt_hash della ricevuta 3 (invalida la firma della ricevuta 3), OSe la chiave privata è custodita in un hardware key vault e pubblichi la chiave pubblica con ogni ricevuta, nessun attacco è fattibile senza essere scoperto.
Il notebook guida attraverso:
previous_receipt_hash di ogni ricevuta corrisponda all’effettivo hash della ricevuta precedente.Così produci una traccia di audit che un revisore esterno può verificare senza doversi fidare di te.
Questa è la parte più importante della lezione. Le ricevute sono potenti ma i loro poteri sono limitati.
Le ricevute provano tre cose:
Le ricevute NON provano:
policy_id sia stata effettivamente valutata, o che avrebbe permesso quell’azione se controllata. La ricevuta registra ciò che è stato dichiarato, non ciò che è stato applicato.Questo confine è importante per due motivi:
Un errore comune è assumere che “avere ricevute” significhi “essere governati.” Non è così. Le ricevute sono la base. Il governo è il sistema che costruisci sopra.
Il codice Python di questa lezione è volutamente minimale per farti 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 revisionate.
Usare una libreria di ricevute per la produzione. Vari progetti open source implementano lo stesso schema con funzionalità aggiuntive (rotazione 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) offrono un’implementazione Node per firmare e verificare ricevute offline, pensata per avvolgere qualsiasi server MCP con una traccia di audit tamper-evident.pip install nobulex) fornisce lo stesso schema di firma Ed25519 + JCS in Python con integrazioni LangChain e CrewAI, includendo vettori di test di cross-validazione pubblicati e una mappatura di conformità contribuita via OWASP PR #2210.La scelta tra implementare da zero e usare una libreria è come scegliere tra scrivere la propria libreria JWT o usarne una già collaudata: entrambe valide; la libreria risparmia tempo e riduce la superficie di audit; l’approccio da zero ti costringe a capire ogni primitiva. Questa lezione insegna il percorso da zero così da darti basi 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 fosse governata da una politica più permissiva. La firma copriva il payload originale. Cosa succede durante la verifica?
3. Perché la ricevuta include un tool_args_hash e result_hash invece degli argomenti e risultati in chiaro?
4. Il campo previous_receipt_hash collega ogni ricevuta al suo predecessore. Se un attaccante cancella silenziosamente una ricevuta in mezzo alla catena, cosa diventa invalido?
5. Una ricevuta verifica correttamente. Ciò prova che l’azione dell’agente era corretta, fondata o conforme alla politica?
Apri code_samples/18-signed-receipts.ipynb e completa tutte e quattro le sezioni:
Sfida aggiuntiva 2: crea un hash SHA-256 di due tue ricevute insieme (concatena i loro byte canonici in ordine deterministico) e incorpora il digest risultante come un nuovo campo in una terza ricevuta prima di firmarla. Verifica che tutte e tre le ricevute si verifichino correttamente. Hai appena costruito una prova di inclusione a un passaggio: chiunque abbia la terza ricevuta può dimostrare che le prime due esistevano al momento della firma, senza bisogno di rivelarne il contenuto. Questo è il modello usato dai 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 quegli strati. Quando distribuisci agenti in carichi di lavoro regolamentati, flussi di lavoro multi-organizzazione o qualsiasi ambiente in cui un futuro revisore non può essere dato per scontato che ti fidi, le ricevute sono il modo per rendere onesta la traccia di audit.
Il messaggio più importante: le ricevute provano chi ha detto cosa e quando. Non provano che ciò che è stato detto fosse vero o giusto. Tieni ben chiara questa distinzione. È la differenza tra un sistema di provenienza onesto e uno ingannevole.
Quando sei pronto a passare da questa lezione a distribuire agenti firmati da ricevute in un ambiente reale:
https://your-org.example.com/.well-known/agent-keys.json.Unisciti al Microsoft Foundry Discord per incontrare altri studenti, partecipare agli orari di ufficio e ottenere risposte alle tue domande sugli agenti AI.
Questa lezione copre la firma di singole ricevute e sequenze concatenate tramite hash. Le stesse primitive compongono diversi modelli più avanzati che potresti incontrare man mano che la tua postura di governance matura:
authorization_*) e una metà post-esecuzione (result_*) con firme indipendenti, utile quando la decisione di autorizzazione e il risultato osservato sono prodotti da attori diversi o in tempi diversi. Questo si aggiunge in modo compositivo al formato di ricevuta insegnato in questa lezione.result_hash. I payload reali sono spesso più completi di un singolo risultato di chiamata: il ragionamento pre-decisione (predizione del modello, opzioni considerate, evidenze e loro completezza, postura di rischio, catena di responsabilità, esito di gate) può vivere tutto nel payload, sigillato da una singola ricevuta. Questo mantiene minimalista il formato della ricevuta lasciando evolvere gli schemi dei payload dominio per dominio.signature.alg può portare ML-DSA-65 (lo standard NIST di firma post-quantistica) 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 dai mantenitori 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.