Assista ao vídeo da lição: Protegendo Agentes de IA com Recibos Criptográficos
(Vídeo da lição e miniatura serão adicionados pela equipe de conteúdo da Microsoft após a mesclagem, seguindo o padrão das lições 14 / 15.)
Esta lição abordará:
Após concluir esta lição, você saberá como:
Imagine que você tenha implementado um agente de IA para a Contoso Travel. O agente lê pedidos dos clientes, consulta uma API de voos para buscar opções e reserva assentos em nome do cliente. No último trimestre, o agente processou 50.000 reservas.
Hoje chega um auditor. Ele faz uma pergunta simples: “Mostre-me o que seu agente fez.”
Você entrega seus arquivos de log. O auditor os examina e faz uma pergunta mais difícil: “Como posso saber que estes logs não foram editados?”
Este é o problema da trilha de auditoria. A maioria das implementações de agentes atualmente depende de:
Nenhum desses pode responder à pergunta do auditor sem que ele tenha que confiar em alguém (você, seu provedor de nuvem, seu fornecedor de banco de dados). Para uso interno, essa confiança é geralmente aceitável. Para cargas reguladas (finanças, saúde, qualquer coisa sujeita à Lei de IA da UE), não é.
Recibos criptográficos resolvem isso tornando cada ação do agente independentemente verificável. O auditor não precisa confiar em você. Ele só precisa da sua chave pública e do próprio recibo.
Um recibo é um objeto JSON que registra o que um agente fez, assinado com uma assinatura digital.
flowchart LR
A[Agente invoca uma ferramenta] --> B[Construir carga útil do recibo]
B --> C[Canonicalizar JSON RFC 8785]
C --> D[Hash SHA-256]
D --> E[Assinar Ed25519]
E --> F[Recibo com assinatura]
F --> G[Auditor verifica offline]
G --> H{Assinatura válida?}
H -- yes --> I[Prova à prova de adulteração]
H -- no --> J[Recibo rejeitado]
Um recibo mínimo se parece com isto:
{
"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..."
}
}
Três propriedades fazem o trabalho:
A assinatura. O recibo é assinado pelo gateway do agente usando uma chave privada Ed25519. Qualquer pessoa com a chave pública correspondente pode verificar a assinatura offline. Qualquer alteração em algum campo invalida a assinatura.
Codificação canônica. Antes de assinar, o recibo é serializado usando o Esquema de Canonicalização JSON (JCS, RFC 8785). Isso garante que duas implementações que produzem o mesmo recibo lógico gerem uma saída byte-idêntica. Sem canonização, diferentes serializadores JSON gerariam assinaturas diferentes para o mesmo conteúdo.
Encadeamento por hash. O campo previous_receipt_hash liga cada recibo ao anterior. Remover ou reordenar um recibo quebra todos os que vieram depois. A adulteração torna-se visível em nível de cadeia, mesmo se assinaturas individuais forem burladas.
Juntas, essas propriedades garantem três coisas:
Você não precisa de uma biblioteca especial para produzir um recibo. As primitivas criptográficas estão amplamente disponíveis e a lógica tem poucas dezenas de linhas em Python.
Os exercícios práticos em code_samples/18-signed-receipts.ipynb detalham o fluxo completo. A versão resumida:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # JSON canônico 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()}"
# Gerar ou carregar uma chave de assinatura (em produção, armazenar em um cofre de chaves)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Construir o conteúdo do recibo (ainda sem assinatura)
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,
}
# Canonicalizar, gerar hash, assinar.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature
# Anexar um objeto de assinatura estruturado.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Esse é todo o pipeline de assinatura. Os exercícios no notebook explicam cada passo.
A verificação é a operação 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:
# A assinatura é um objeto estruturado: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Reconstrua o conteúdo que foi realmente assinado (tudo exceto a assinatura).
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
Esta função recebe um recibo e retorna True se a assinatura for válida, False caso contrário. Sem chamadas de rede, sem dependência de serviço, sem necessidade de confiar em terceiros.
Para ver a detecção de adulteração em ação, o notebook guia por:
tool_args_hash.Esta é a demonstração prática de que recibos são evidência de adulteração: qualquer modificação, por menor que seja, quebra a assinatura.
Um único recibo assinado protege uma ação. Uma cadeia de recibos protege uma sequência.
flowchart LR
R0[Recibo 0<br/>gênese] --> R1[Recibo 1]
R1 --> R2[Recibo 2]
R2 --> R3[Recibo 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Cada recibo registra o hash do recibo anterior. Para remover silenciosamente o recibo 2, um atacante precisaria:
previous_receipt_hash do recibo 3 (quebrando a assinatura do recibo 3), OUSe a chave privada estiver num cofre de hardware e você publicar a chave pública com cada recibo, nenhum desses ataques é viável sem ser detectado.
O notebook orienta:
previous_receipt_hash de cada recibo corresponde ao hash real do recibo anterior.Assim você produz uma trilha de auditoria que um auditor externo pode verificar sem precisar confiar em você.
Esta é a seção mais importante desta lição. Recibos são poderosos mas seu poder é limitado.
Recibos comprovam três coisas:
Recibos NÃO comprovam:
policy_id foi realmente avaliada, ou que teria permitido esta ação se verificada. O recibo registra o que foi alegado, não o que foi imposto.Esse limite é importante por duas razões:
Um erro comum é assumir que “temos recibos” significa “estamos governados”. Não significa. Recibos são uma base. Governança é o sistema que você constrói sobre ela.
O item 3 acima merece uma seção própria: um recibo de ação diz “esta chave assinou este conteúdo”, nunca “um humano autorizou isto.” Para ações de alto risco (reembolsos, exclusões, transferências bancárias), os frameworks de governança exigem cada vez mais exatamente essa declaração faltante, e ela pode ser produzida com as mesmas primitivas que você já construiu nesta lição.
O notebook complementar code_samples/human-authorization-receipts.ipynb adiciona um segundo tipo de recibo, human.approval.v1, na mesma forma de envelope dos recibos da lição (uma carga tipada assinada por Ed25519 sobre seu SHA-256 canônico, com o objeto signature fora dos bytes assinados). Um aprovador nomeado assina a ação canônica completa e seu resumo antes da execução; o recibo de ação do agente carrega o mesmo resumo da ação e uma parent_approval_ref, o receipt_hash da aprovação, a mesma convenção que previous_receipt_hash na cadeia que você construiu acima. Um único verify_chain percorre ambos artefatos sob registros de chaves fixas separados (chaves de aprovador versus chaves de agente), assim o caminho de código é compartilhado, mas as autoridades nunca são.
A propriedade que isso garante, exposta cuidadosamente: o humano aprovou esta ação exata, e o agente executou exatamente essa ação aprovada. As verificações de recusa no notebook são o que tornam essa propriedade real em vez de apenas afirmada:
Cada falha é recusada com uma razão distinta, para que um auditor lendo uma recusa possa saber se a autoridade expirou ou se a ação executada mudou. A regra que o notebook ensina: uma aprovação assinada não é autoridade por si só. Autoridade existe apenas se ambos os recibos ainda vinculam à mesma ação canônica no momento da execução. O caminho da coassinatura no mesmo Internet-Draft que esta lição segue (draft-farley-acta-signed-receipts) é o formato padrão desse padrão.
O código Python desta lição é intencionalmente mínimo para que você possa ler cada linha e entender exatamente o que está acontecendo. Na produção, você tem duas opções:
Construir diretamente sobre as primitivas criptográficas. As 50 linhas que você viu são suficientes para muitos casos de uso. PyNaCl (Ed25519) e o pacote jcs (JSON canônico) são bibliotecas bem mantidas e auditadas.
Usar uma biblioteca de recibos para produção. Vários projetos open-source implementam o mesmo padrão com recursos adicionais (rotação de chaves, verificação em lote, distribuição de Conjuntos JWK, integração com motores de política):
draft-farley-acta-signed-receipts, revisão 02) atualmente em processo de padronização, com uma suíte de conformidade compartilhada (agent-governance-testvectors) que implementações independentes cruzam-verificam para garantir saída canônica byte-idêntica.protect-mcp (npm) e @veritasacta/verify (npm) fornecem uma implementação Node para assinatura de recibos e verificação offline, destinados a envolver qualquer servidor MCP com uma trilha de auditoria que evidencie adulteração, incluindo um fluxo de coassinatura em espera no qual uma ação pausada emite um recibo de aprovação vinculado ao resumo da ação (com suporte WebAuthn no fluxo desktop), o mesmo padrão de recibo de aprovação do notebook de autorização humana acima.pip install nobulex) oferece o mesmo padrão de assinatura Ed25519 + JCS em Python com integrações LangChain e CrewAI, incluindo vetores de teste de validação cruzada publicados e um mapeamento de conformidade contribuído via OWASP PR #2210.A decisão entre implementar seu próprio código e usar uma biblioteca espelha a decisão entre escrever sua própria biblioteca JWT e usar uma testada: ambas são razoáveis; a biblioteca economiza tempo e reduz a superfície de auditoria; a abordagem do zero força você a entender cada primitiva. Esta lição ensina o caminho do zero para que você tenha a base para ambas as escolhas.
Teste seu entendimento antes de avançar para o exercício prático.
1. Um recibo é assinado com a chave privada Ed25519 do agente. O auditor tem apenas a chave pública. O auditor pode verificar o recibo offline?
2. Um atacante modifica o campo policy_id de um recibo para alegar que foi governado por uma política mais permissiva. A assinatura foi feita sobre a carga original. O que acontece na verificação?
3. Por que o recibo inclui um tool_args_hash e result_hash em vez dos argumentos e resultado brutos?
4. O campo previous_receipt_hash vincula cada recibo ao seu predecessor. Se um atacante excluir silenciosamente um recibo do meio de uma cadeia, o que se torna inválido?
5. Um recibo é verificado com sucesso. Isso prova que a ação do agente foi correta, sólida ou conforme a política?
Abra code_samples/18-signed-receipts.ipynb e complete todas as quatro seções:
Desafio extra 1: estenda o esquema do recibo com um campo adicional de sua escolha (por exemplo, um ID de requisição para rastreamento), atualize a lógica de assinatura canônica para incluí-lo e confirme que o recibo ainda passa pela verificação. Depois modifique o campo após a assinatura e confirme que a verificação falha. Isso força a entender como cada byte da codificação canônica contribui para a assinatura.
Desafio extra 2: Faça o hash SHA-256 de dois de seus recibos juntos (concatene seus bytes canônicos em uma ordem determinística) e insira o digest resultante como um novo campo em um terceiro recibo antes de assinar. Verifique que os três recibos ainda passam pela verificação. Você acaba de construir uma prova de inclusão de um passo: qualquer um que tenha o terceiro recibo pode provar que os dois primeiros existiam no momento em que foi assinado, sem precisar revelar seus conteúdos. Este é o padrão usado em recibos de divulgação seletiva em grande escala (compromissos Merkle, RFC 6962).
Recibos criptográficos dão aos agentes de IA uma trilha de auditoria que é:
Eles não substituem validação de entrada, aplicação de políticas ou infraestrutura de identidade. São a base para essas camadas. Quando você implanta agentes em cargas reguladas, fluxos entre múltiplas organizações, ou qualquer situação onde um auditor futuro não pode ser presumido confiar em você, recibos são como tornar a trilha de auditoria honesta.
A lição mais importante: recibos provam quem disse o quê e quando. Não provam que o que foi dito é verdade ou correto. Mantenha essa distinção firmemente. Essa é a diferença entre um sistema de proveniência honesto e um enganoso.
Quando estiver pronto para avançar desta lição para implantar agentes com recibos assinados em um ambiente real:
https://your-org.example.com/.well-known/agent-keys.json.Junte-se ao Microsoft Foundry Discord para encontrar outros aprendizes, participar de horas de atendimento e tirar suas dúvidas sobre Agentes de IA.
Esta lição cobre assinatura de recibo único e sequências em cadeia de hash. Os mesmos primitivos compõem vários padrões avançados que você pode encontrar conforme sua postura de governança amadurece:
authorization_*) e metade pós-execução (result_*) com assinaturas independentes, útil quando a decisão de autorização e o resultado observado são produzidos por atores diferentes ou em momentos diferentes. Isso é um acréscimo composicional ao formato de recibo ensinado nesta lição.result_hash. Cargas no mundo real são frequentemente mais ricas que o resultado de uma única chamada de ferramenta: raciocínio pré-decisão (previsão de modelo, opções consideradas, evidência e sua completude, postura de risco, cadeia de responsabilidade, resultado do gate) podem estar dentro da carga útil, selados por um único recibo. Isso mantém o formato do recibo minimalista enquanto permite que esquemas de carga evoluam domínio por domínio.signature.alg pode carregar ML-DSA-65 (padrão NIST de assinatura pós-quântica) quando você precisar migrar. Planeje um período de transição em que os recibos sejam assinados duplamente.Aviso Legal: Este documento foi traduzido usando o serviço de tradução por IA Co-op Translator. Embora nos esforcemos pela precisão, por favor, esteja ciente de que traduções automatizadas podem conter erros ou imprecisões. O documento original em seu idioma nativo deve ser considerado a fonte autorizada. Para informações críticas, recomenda-se tradução profissional humana. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.