Assista ao vídeo da lição: Protegendo Agentes de IA com Recibos Criptográficos
(Vídeo da lição e miniatura a serem adicionados pela equipe de conteúdo da Microsoft após a mesclagem, seguindo o padrão da lição 14 / 15.)
Esta lição abordará:
Após completar esta lição, você saberá como:
Imagine que você implantou um agente de IA para a Contoso Travel. O agente lê as solicitações 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 um auditor chega. Ele faz uma pergunta simples: “Mostre o que seu agente fez.”
Você entrega seus arquivos de log. O auditor os analisa e faz uma pergunta mais difícil: “Como sei que esses logs não foram editados?”
Este é o problema da trilha de auditoria. A maioria das implantações de agentes hoje depende de:
Nenhum deles pode responder à pergunta do auditor sem exigir que o auditor confie 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 regulamentadas (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 verificável de forma independente. O auditor não precisa confiar em você. Ele precisa apenas de 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 --> E[Assinar em Ed25519 bytes canônicos]
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 fica assim:
{
"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 adulteração em qualquer campo invalida a assinatura.
Codificação canônica. Antes de assinar, o recibo é serializado usando o JSON Canonicalization Scheme (JCS, RFC 8785). Isso assegura que duas implementações que produzem o mesmo recibo lógico produzam saída idêntica em bytes. Sem a canonicalização, diferentes serializadores JSON produziriam 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 recibos seguintes. A adulteração torna-se visível no nível da cadeia mesmo que assinaturas individuais sejam burladas.
Juntas, essas propriedades fornecem três garantias:
Você não precisa de uma biblioteca especial para produzir um recibo. Os primitivos criptográficos estão amplamente disponíveis e a lógica são poucas dezenas de linhas em Python.
Os exercícios práticos em code_samples/18-signed-receipts.ipynb percorrem 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 payload 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 e assinar diretamente os bytes JCS. PureEdDSA faz hash internamente.
canonical_bytes = canonicalize(payload)
signature_bytes = signing_key.sign(canonical_bytes).signature
# Anexar um objeto de assinatura estruturado.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Este é todo o pipeline de assinatura. Os exercícios no notebook percorrem cada passo.
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 payload que foi realmente assinado (tudo exceto a assinatura).
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
Esta função recebe um recibo e retorna True se a assinatura for válida, False caso contrário. Sem chamada 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 percorre:
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 invasor precisaria:
previous_receipt_hash do recibo 3 (quebrando a assinatura do recibo 3), OUSe a chave privada estiver em um cofre de chaves de hardware e você publicar a chave pública com cada recibo, nenhum desses ataques é viável sem ser detectado.
O notebook percorre:
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 provam três coisas:
Recibos NÃO provam:
policy_id foi realmente avaliada, ou que ela teria permitido essa ação se fosse verificada. O recibo registra o que foi afirmado, não o que foi aplicado.Esse limite é importante por duas razões:
Um erro comum é assumir que “temos recibos” significa “estamos governados”. Não é. Recibos são uma base. Governança é o sistema que você constrói a partir deles.
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”. Para ações de alto risco (reembolsos, exclusões, transferências bancárias), estruturas de governança cada vez mais exigem exatamente essa declaração ausente, e ela pode ser produzida com os mesmos primitivos 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 estrutura de envelope dos recibos da lição (uma carga útil tipada assinada por Ed25519 sobre seus bytes canônicos JCS, com o objeto signature fora dos bytes assinados). Um aprovador nomeado assina a ação canônica completa e seu digest antes da execução; o recibo de ação do agente carrega o mesmo digest de 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 verify_chain passa por ambos os artefatos sob registros de chave fixos e separados (chaves de aprovador vs chaves de agente), assim o caminho de código é compartilhado, mas as autoridades nunca são.
A propriedade que isso garante, declarada cuidadosamente: o humano aprovou esta ação exata, e o agente executou exatamente essa ação aprovada. Os testes de recusa do notebook são o que tornam essa propriedade real e não mera assertiva:
Cada falha recusa com uma razão distinta, assim um auditor lendo uma recusa pode dizer se a autoridade ficou obsoleta ou 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 recibo de aprovação humana é uma composição educativa definida por esta lição, não um tipo de recibo definido pelo draft-farley-acta-signed-receipts.
O código Python desta lição é intencionalmente minimalista para que você possa ler cada linha e entender exatamente o que está acontecendo. Em produção, você tem duas opções:
Construir diretamente sobre os primitivos criptográficos. As 50 linhas que você viu acima 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 JWK Set, integração com motores de política):
draft-farley-acta-signed-receipts, revisão 02). O recibo educacional plano desta lição difere do envelope {payload, signature} do rascunho e não é apresentado como uma implementação conforme. O rascunho publica uma suíte de conformidade compartilhada (agent-governance-testvectors) para implementações que visam seu formato.protect-mcp (npm) e @veritasacta/verify (npm) fornecem uma implementação Node da assinatura e verificação offline de recibos, destinada a envolver qualquer servidor MCP com uma trilha de auditoria resistente a adulteração, incluindo um fluxo de co-assinatura onde uma ação pausada emite um recibo de aprovação vinculado ao digest da ação (com suporte a WebAuthn no fluxo desktop), o mesmo padrão de recibo de aprovação do notebook de autorização humana acima.pip install nobulex) fornece 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 construir sua própria solução e usar uma biblioteca é análoga à escolha 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 primitivo. Esta lição ensina o caminho do zero para que você tenha a base para qualquer escolha.
Teste seu entendimento antes de seguir para o exercício prático.
1. Um recibo é assinado com a chave privada Ed25519 do agente. O auditor possui apenas a chave pública. O auditor pode verificar o recibo offline?
2. Um invasor 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 útil original. O que acontece durante a verificação?
3. Por que o recibo inclui um tool_args_hash e result_hash em vez dos argumentos brutos e resultado?
4. O campo previous_receipt_hash vincula cada recibo ao seu predecessor. Se um atacante remover silenciosamente um recibo do meio de uma cadeia, o que se torna inválido?
5. Um recibo verifica corretamente. Isso prova que a ação do agente foi correta, sólida ou compatível com 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 canônica de assinatura para incluí-lo e confirme que o recibo ainda passa pela verificação. Então modifique o campo após a assinatura e confirme que a verificação falha. Isso força você 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 incorpore o resumo resultante como um novo campo em um terceiro recibo antes de assiná-lo. Verifique que os três recibos ainda passam pela verificação. Você acabou de construir uma prova de inclusão de um passo: qualquer pessoa que tenha o terceiro recibo pode provar que os dois primeiros existiam no momento da assinatura, sem precisar revelar seus conteúdos. Este é o padrão que os recibos de divulgação seletiva usam em larga escala (compromissos Merkle, RFC 6962).
Recibos criptográficos dão aos agentes de IA uma trilha de auditoria que é:
Eles não são substitutos para validação de entrada, aplicação de política ou infraestrutura de identidade. São uma base para essas camadas. Quando você implanta agentes em cargas de trabalho reguladas, fluxos de trabalho com múltiplas organizações, ou qualquer contexto em que um auditor futuro não possa supor confiança em você, os recibos são como você torna a trilha de auditoria honesta.
A lição mais importante: recibos provam quem disse o quê e quando. Eles não provam que o que foi dito é verdadeiro ou correto. Mantenha essa distinção clara. É a diferença entre um sistema de procedência honesto e um enganoso.
Quando estiver pronto para avançar desta lição para a implantação de 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 horários de atendimento e tirar suas dúvidas sobre Agentes de IA.
Esta lição cobre assinatura de recibos únicos e sequências em cadeia de hash. Os mesmos primitvos compõem vários padrões mais avançados que você pode encontrar à medida que sua postura de governança amadurece:
authorization_*) e 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 se soma ao formato de recibo ensinado aqui.result_hash. Cargas reais são frequentemente mais ricas do que o resultado de uma única chamada de ferramenta: raciocínio pré-decisão (predição de modelo, opções consideradas, evidências e sua completude, postura de risco, cadeia de responsabilidade, resultado do gate) podem estar dentro da carga, selados por um único recibo. Isso mantém o formato do recibo minimalista enquanto permite evolução dos esquemas de carga por domínio.signature.alg pode carregar ML-DSA-65 (padrão pós-quântico NIST) quando for necessário 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.