ai-agents-for-beginners

Guarda il video della lezione: Proteggere gli agenti AI con Ricevute Criptografiche

(Video della lezione e anteprima verranno aggiunti dal team contenuti di Microsoft dopo la fusione, in linea con il modello delle lezioni 14 / 15.)

Proteggere gli agenti AI con Ricevute Criptografiche

Introduzione

Questa lezione coprirà:

Obiettivi di apprendimento

Dopo aver completato questa lezione, saprai come:

Il problema: la traccia di controllo del tuo agente

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 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 richiedere che egli si fidi di qualcuno (te, il tuo provider cloud, il tuo fornitore di database). Per uso interno, quella fiducia è spesso accettabile. Per carichi di lavoro regolamentati (finanza, sanità, qualsiasi cosa soggetta all’AI Act dell’UE), non lo è.

Le ricevute criptografiche risolvono questo rendendo ogni azione dell’agente indipendentemente verificabile. Il revisore non deve fidarsi di te. Serve solo la tua chiave pubblica e la ricevuta stessa.

Che cos’è una Ricevuta Criptografica?

Una ricevuta è un oggetto JSON che registra cosa ha fatto un agente, 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 --> D[Hash SHA-256]
    D --> E[Firma Ed25519]
    E --> F[Ricevuta con firma]
    F --> G[L'auditor verifica offline]
    G --> H{Firma valida?}
    H -- yes --> I[Prova a prova di manomissione]
    H -- no --> J[Ricevuta respinta]

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à sono responsabili:

  1. 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 qualsiasi campo invalida la firma.

  2. Codifica canonica. Prima di firmare, la ricevuta viene serializzata usando lo JSON Canonicalization Scheme (JCS, RFC 8785). Ciò garantisce che due implementazioni che producono la stessa ricevuta logica producano un output byte-identico. Senza la canonicalizzazione, diversi serializzatori JSON produrrebbero firme diverse per lo stesso contenuto.

  3. Concatenazione tramite hash. Il campo previous_receipt_hash collega ogni ricevuta a quella precedente. Rimuovere o riordinare una ricevuta rompe ogni ricevuta successiva. La manomissione diventa visibile a livello di catena anche se vengono superate le singole firme.

Insieme queste proprietà forniscono tre garanzie:

Produrre una Ricevuta in Python

Non ti 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 mostrano il flusso completo. 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 per la firma (in produzione, memorizzala in un vault 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)),
    },
}

Questa è l’intera pipeline di firma. Gli esercizi nel notebook guidano ogni passaggio.

Verificare una Ricevuta e Rilevare Manomissioni

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 in terze parti richiesta.

Per vedere la rilevazione di manomissioni in azione, il notebook mostra:

  1. Produrre una ricevuta valida e confermare la verifica.
  2. Modificare un byte del campo tool_args_hash.
  3. Rieseguire la verifica e osservare il fallimento.

Questa è la dimostrazione pratica che le ricevute sono tamper-evident: qualsiasi modifica, anche minima, rompe la firma.

Concatenare Ricevute per Agenti a Più Passi

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:

Se la chiave privata si trova in un hardware key vault e pubblichi la chiave pubblica con ogni ricevuta, nessuno dei due attacchi è fattibile senza essere rilevato.

Il notebook mostra:

  1. Costruire una catena di tre ricevute.
  2. Verificare che il previous_receipt_hash di ogni ricevuta corrisponda all’hash reale della ricevuta precedente.
  3. Manomettere una ricevuta in mezzo e vedere la catena interrompersi proprio in quel punto.

Così produci una traccia di controllo che un revisore esterno può verificare senza dover fidarsi di te.

Cosa dimostrano le Ricevute (e cosa non dimostrano)

Questa è la sezione più importante di questa lezione. Le ricevute sono potenti ma il loro potere è limitato.

Le ricevute dimostrano tre cose:

  1. Attribuzione: una chiave specifica ha firmato un payload specifico.
  2. Integrità: il payload non è stato modificato dopo la firma.
  3. Ordinamento: questa ricevuta è venuta dopo quella nella catena di hash.

Le ricevute NON dimostrano:

  1. Correttezza: che l’azione dell’agente fosse la corretta. Una ricevuta può essere firmata per una risposta errata tanto facilmente quanto per una risposta corretta.
  2. Conformità alla politica: che la politica referenziata in policy_id sia stata effettivamente valutata, o che avrebbe permesso questa azione se verificata. La ricevuta registra ciò che è stato dichiarato, non ciò che è stato applicato.
  3. Identità oltre la chiave: la ricevuta dice “questa chiave ha firmato questo contenuto.” Non dice “questo umano ha autorizzato questo.” Collegare una chiave a una persona o organizzazione richiede un’infrastruttura di identità separata (una directory, un registro di chiavi pubbliche, ecc.).
  4. Veridicità degli input: se l’agente riceve un prompt manipolato e agisce su di esso, la ricevuta registra fedelmente l’azione. Le ricevute sono a valle della validazione degli input, non un suo sostituto.

Questo confine è importante per due motivi:

Un errore comune è assumere che “abbiamo ricevute” significhi “siamo governati.” Non è così. Le ricevute sono una fondazione. La governance è il sistema che costruisci sopra.

Dimostrare che un Umano ha Approvato l’Azione Esatta

Il punto 3 sopra merita una sezione propria: 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ù esattamente questa dichiarazione mancante, e si può produrre con le stesse primitive che hai già costruito in questa lezione.

Il notebook successivo code_samples/human-authorization-receipts.ipynb aggiunge un secondo tipo di ricevuta, human.approval.v1, nella stessa forma di busta delle ricevute della lezione (un payload tipizzato firmato da Ed25519 sulla sua SHA-256 canonica, con l’oggetto signature fuori dai byte firmati). Un approvatore nominato firma l’azione canonica completa e il suo digest prima dell’esecuzione; la ricevuta dell’azione dell’agente porta lo stesso digest dell’azione e un parent_approval_ref, l’receipt_hash dell’approvazione, la stessa convenzione del previous_receipt_hash nella catena che hai costruito sopra. Una singola verify_chain passa entrambi gli artefatti sotto registri di chiavi fissati separati (chiavi approvatore vs chiavi agente), così il percorso del codice è condiviso ma le autorità mai.

La proprietà acquistata, spiegata con attenzione: l’umano ha approvato questa esatta azione, e l’agente ha eseguito esattamente quell’azione approvata. Gli strumenti di rifiuto nel notebook sono ciò che rende reale la proprietà invece che solo affermata:

Ogni fallimento rifiuta per un motivo distinto, così un revisore che legge un rifiuto può capire se l’autorità è scaduta o se l’azione eseguita è cambiata. La regola che insegna il notebook: un’approvazione firmata non è autorità da sola. L’autorità esiste solo se entrambe le ricevute legano ancora la stessa azione canonica al momento dell’esecuzione. Il percorso di co-firma nell’Internet-Draft seguito da questa lezione (draft-farley-acta-signed-receipts) è la forma standardizzata di questo modello.

Riferimenti per la Produzione

Il codice Python in questa lezione è intenzionalmente minimo così puoi leggere ogni riga e capire esattamente cosa succede. In produzione, hai due opzioni:

  1. 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.

  2. Usare una libreria di ricevute di produzione. Diversi progetti open-source implementano lo stesso modello con funzionalità aggiuntive (rotazione chiavi, verifica batch, distribuzione Set JWK, integrazione con motori di policy):

    • Il formato di ricevuta usato in questa lezione segue un Internet-Draft IETF (draft-farley-acta-signed-receipts, revisione 02) attualmente nel processo di standardizzazione, con una suite di conformità condivisa (agent-governance-testvectors) che implementazioni indipendenti incrociano per verificare un output canonico byte-identico.
    • Il Microsoft Agent Governance Toolkit compone ricevute con decisioni policy basate su Cedar; vedi il Tutorial 33 in quel repository per un esempio end-to-end.
    • I pacchetti protect-mcp (npm) e @veritasacta/verify (npm) forniscono un’implementazione Node di firma di ricevute e verifica offline, pensata per incapsulare qualsiasi server MCP con traccia di controllo tamper-evident, incluso un flusso di co-firma tenuto in sospeso in cui un’azione in pausa emette una ricevuta di approvazione legata al digest dell’azione (WebAuthn supportato nel flusso desktop), lo stesso modello di ricevuta di approvazione umano sopra.
    • Il SDK Python nobulex (pip install nobulex) fornisce lo stesso modello di firma Ed25519 + JCS in Python con integrazioni LangChain e CrewAI, inclusi vettori di test pubblicati per la convalida incrociata e una mappatura di conformità contribuita tramite OWASP PR #2210.

La decisione tra costruire la tua soluzione o usare una libreria rispecchia la scelta tra scrivere la tua libreria JWT e usarne una testata: entrambe sono ragionevoli; la libreria fa risparmiare tempo e riduce la superficie di controllo; il metodo da zero ti obbliga a capire ogni primitiva. Questa lezione insegna il percorso da zero così hai la base per entrambe le scelte.

Verifica delle conoscenze

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ò il revisore verificare la ricevuta offline?

Risposta Sì. La verifica Ed25519 richiede solo la chiave pubblica e i byte firmati. Nessuna chiamata di rete, nessuna dipendenza da servizi. Questa è la proprietà che rende le ricevute utili in ambienti air-gapped, multi-organizzazione, o di verifica a bassa fiducia.

2. Un attaccante modifica il campo policy_id di una ricevuta per affermare che fosse governata da una politica più permissiva. La firma era sul payload originale. Cosa succede durante la verifica?

Risposta La verifica fallisce. La firma è stata calcolata sui byte canonici del payload originale; modificare qualsiasi campo cambia i byte canonici, il che cambia l'hash SHA-256, rendendo la firma invalida. L’attaccante avrebbe bisogno della chiave privata per produrre una nuova firma valida, cosa che non possiede.

3. Perché la ricevuta include un tool_args_hash e un result_hash invece degli argomenti grezzi e del risultato?

Risposta Due motivi. Primo, la ricevuta potrebbe dover essere archiviata o trasmessa in ambienti in cui la perdita del contenuto grezzo (PII, dati aziendali) è un problema. L’hashing mantiene la ricevuta piccola e il contenuto privato; l’auditor verifica che l’hash corrisponda a una copia memorizzata separatamente del contenuto effettivo. Secondo, gli hash hanno una dimensione fissa; una ricevuta con hash ha una dimensione limitata indipendentemente da quanto grandi fossero input e output.

4. Il campo previous_receipt_hash collega ogni ricevuta al suo predecessore. Se un attaccante cancella silenziosamente una ricevuta da metà catena, cosa diventa invalido?

Risposta Ogni ricevuta che è venuta dopo quella cancellata. I loro campi `previous_receipt_hash` non corrispondono più alla catena effettiva (perché la ricevuta a cui facevano riferimento non esiste più, oppure la catena ora punta a un predecessore diverso). Per nascondere la cancellazione, l’attaccante dovrebbe rifirmare ogni ricevuta successiva, cosa che richiede la chiave privata.

5. Una ricevuta verifica correttamente. Ciò dimostra che l’azione dell’agente è stata corretta, valida o conforme alla policy?

Risposta No. Una ricevuta valida prova tre cose: attribuzione (questa chiave ha firmato questo contenuto), integrità (il contenuto non è cambiato) e ordine (questa ricevuta è venuta dopo quell’altra). NON prova che l’azione sia stata corretta, che la policy indicata in `policy_id` sia stata realmente valutata, o che l’agente abbia seguito ogni regola. Le ricevute rendono il comportamento dell’agente controllabile, non necessariamente corretto. Questo è il confine più importante nella lezione.

Esercizio Pratico

Apri code_samples/18-signed-receipts.ipynb e completa tutte e quattro le sezioni:

  1. Sezione 1: Firma la tua prima ricevuta e verifica la sua validità.
  2. Sezione 2: Manometti la ricevuta e osserva il fallimento della verifica.
  3. Sezione 3: Costruisci una catena di tre ricevute e verifica l’integrità della catena.
  4. Sezione 4: Applica il modello a un agente costruito con Microsoft Agent Framework: incapsula una chiamata a uno strumento nella firma della ricevuta, quindi verifica la ricevuta indipendentemente.

Sfida avanzata 1: estendi lo schema della ricevuta con un campo aggiuntivo a tua scelta (ad esempio, un ID di richiesta per il tracciamento), aggiorna la logica di firma canonica per includerlo e conferma che la ricevuta pass inalterata attraverso la verifica. Poi modifica quel campo dopo la firma e conferma che la verifica fallisce. Questo ti costringe a capire come ogni byte della codifica canonica contribuisce alla firma.

Sfida avanzata 2: Applica SHA-256 su due delle tue ricevute insieme (concatenando i loro byte canonici in un ordine deterministico) e incorpora il digest risultante come nuovo campo su una terza ricevuta prima di firmarla. Verifica che tutte e tre le ricevute passino ancora la verifica. Hai appena costruito una prova di inclusione a un passo: chiunque abbia la terza ricevuta può dimostrare che le prime due esistevano al momento della firma, senza doverne rivelare il contenuto. Questo è il modello usato dalle ricevute a rivelazione selettiva su larga scala (impegni Merkle, RFC 6962).

Conclusione

Le ricevute crittografiche danno 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 ambienti regolamentati, flussi di lavoro multi-organizzazione o in qualsiasi contesto dove un futuro auditor non può essere dato per assunto fidarsi di te, le ricevute sono il modo per rendere la traccia di audit onesta.

Il messaggio più importante: le ricevute dimostrano chi ha detto cosa e quando. Non dimostrano che ciò che è stato detto era vero o corretto. Tieni salda questa distinzione. È la differenza tra un sistema di provenienza onesto e uno fuorviante.

Checklist per la Produzione

Quando sei pronto a passare da questa lezione al deploy di agenti con firma di ricevute in un ambiente reale:

Hai altre domande su come mettere in sicurezza gli agenti AI?

Unisciti al Microsoft Foundry Discord per incontrare altri apprendenti, partecipare alle ore di ufficio e risolvere i tuoi dubbi sugli AI Agents.

Oltre questa Lezione

Questa lezione tratta la firma di una singola ricevuta e sequenze concatenate tramite hash. Le stesse primitive compongono diversi schemi più avanzati che potresti incontrare man mano che la tua postura di governance matura:

Risorse Aggiuntive

Lezione precedente

Creazione di agenti AI locali


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.