Guia de segurança de agentes de IA: JSON malicioso, prompt injection, Tool Calling e riscos de dados estruturados

Em 14 de setembro de 2026: quando um agente chama ferramentas e ingere JSON externo, a superfície muda de uma resposta enganosa para uma invocação real. Mapa defensivo: JSON malicioso, injeção indireta, ferramentas com privilégio demais e schemas frouxos.

Conclusão primeiro: em 2026, segurança de AI Agent não é «o modelo vai dizer a frase errada?». É «dados estruturados não confiáveis podem virar uma invocação real de ferramenta?». Um chatbot injetado, no pior caso, emite uma resposta enganosa. Um Agent injetado, no pior caso, envia e-mail, edita arquivos, consulta um banco ou dispara um pagamento. JSON é ao mesmo tempo o contrato e a carga. Prompt Injection muitas vezes não mora na caixa de chat — mora em campos de ticket, páginas web, corpos de e-mail e respostas de API, todos chegando como JSON.

Escrito em 14 de setembro de 2026. Já cobrimos por que Tool Calling depende de JSON Schema, o fluxo de dados JSON do Agent, JSON Schema como contrato e MCP / Skills / Tools / Subagents. Este texto só responde o que arriscam o JSON malicioso, a injeção indireta, o Tool Calling com privilégio demais e os Schema frouxos — e o que o Host deve impor. É um guia defensivo. Não oferece passos de ataque reproduzíveis nem texto de injeção pronto para usar.

Uma tabela: superfície de ataque de chatbot vs Agent

O que o usuário digita é só uma fatia do que um Agent ingere. Na pilha padrão de 2026 o modelo também lê resultados de tools, Resources de MCP, trechos de página, campos de ticket e corpos de e-mail — quase sempre como JSON, ou como strings embrulhadas em JSON. As listas do setor costumam meter isso em Prompt Injection, Insecure Output Handling e Excessive Agency. Você não precisa da taxonomia primeiro. Precisa saber quem está falando e quem está executando.

DimensãoChatbotAgent que pode chamar tools
Modo de falhaUma resposta errada ou induzidaUm efeito colateral real: escrever, requisitar, mutar dados
Entrada não confiávelA mensagem atual do usuárioTexto do usuário + JSON externo + resultados de tools + campos de página/e-mail
ExecutorO modelo só emite textoHost / MCP Server executa arguments
ContratoO prompt (mole)JSON Schema + validação do Host (duro)
Ajuste mínimoAjustes de prompt, política de recusaSchema mais apertado, allowlist de tools, autorização independente

Uma frase: o modelo pode ser enganado; o Host não deve executar junto. O limite de segurança fica entre gerar tool_calls e de fato rodá-los. Validação, autorização e auditoria pertencem ali — não «tratar a saída do modelo como um RPC confiável».

JSON malicioso: campos a mais, confusão de tipos, payloads aninhados

JSON malicioso costuma estar bem formado e ainda assim ser perigoso. Um parser estrito rejeita vírgula final ou comentários. Um parser frouxo, concatenação de strings ou «trate como texto e depois JSON.parse» só atrasam a falha. Em sistemas Agent o estrago começa quando o Host já trata o valor como objeto.

Três formas para defender primeiro (só padrões — não um how-to):

PadrãoO que pareceSe o Host não validaDefesa
Campos a maisUm objeto de negócio com chaves não declaradasVira merge na config, ou segue para o próximo tooladditionalProperties: false; descartar chaves desconhecidas
Confusão de tiposUm number/array chega como string ou objectRamos de autorização erram, ou um blob inteiro vira argumentoFixar type, enum, format
Payload aninhadoUm campo string que embute mais JSON ou um brief longoO texto interno entra no prompt e é lido como instruçõesLimites de comprimento; validar o JSON interno; nunca tratar texto cru como fala de system

Um contrato de parâmetros de tool que «funciona» porque quase não restringe nada, e depois a versão apertada. São amostras defensivas, não material de ataque:

{
  "type": "object",
  "properties": {
    "payload": { "type": "object" }
  }
}

Esse Schema quase não é contrato: qualquer chave, qualquer aninhamento passa. Em produção, allowlist de campos e extras proibidos:

{
  "type": "object",
  "properties": {
    "order_id": { "type": "string", "pattern": "^A-[0-9]{4,8}$" },
    "amount": { "type": "number", "minimum": 0, "maximum": 100000 },
    "note": { "type": "string", "maxLength": 200 }
  },
  "required": ["order_id", "amount"],
  "additionalProperties": false
}

Ao revisar amostras, use o validador JSON deste site contra esse Schema, e o JSON Diff para comparar «arguments que o modelo emitiu» com «o objeto mínimo que você permite». Chaves a mais e tipos invertidos são o primeiro alarme.

Outro esquecimento: não faça merge de objetos não confiáveis na config interna nem em estruturas que tocam a cadeia de protótipos. Num Host JavaScript, chaves desconhecidas num objeto de config podem significar mais do que «um campo extra». O conserto é o mesmo: projete um objeto de allowlist a partir do Schema e passe esse adiante.

Prompt Injection: instruções escondidas em dados estruturados

Injeção direta é o usuário falando na caixa de chat, tentando sobrescrever o system prompt. Injeção indireta combina com o jeito que Agents de 2026 de fato rodam: as instruções se escondem em dados que o modelo vai ler depois — títulos de ticket, corpos de e-mail, parágrafos de página, trechos de PDF, strings JSON devolvidas por outro tool. Modelos não separam naturalmente «regras que o desenvolvedor escreveu» de «frases dentro dos dados».

Dados estruturados deixam isso mais silencioso. Uma nota de cliente é só customer_note numa API. Depois de entrar no contexto, ela divide o fluxo de tokens com o system prompt. Se o Host concatena resultados de tools no turno seguinte do jeito que vieram, um site externo ou um remetente está, na prática, escrevendo prompts para o Agent.

Defesa não é mais uma frase do tipo «por favor ignore instruções maliciosas». Prompts reduzem risco; não são fronteira. Controles mais estáveis:

  • Rotule a procedência — texto externo entra com um papel ou wrapper distinto (por exemplo tool / untrusted), nunca dobrado no system.
  • Encolha a superfície visível — resuma em vez de colar; mande só os campos que você precisa, não o documento JSON inteiro.
  • Ponha portão em ações de alto impacto — transferências, exclusões, envio de saída precisam de um motor de política ou de um humano, independentes do modelo.
  • Trate resultados de tools como dados, não como instruções — valide os retornos com Schema antes de eles voltarem ao prompt.

O mesmo instinto de como o Apple Intelligence protege dados pessoais: o que vaza ou é abusado costuma ser um campo que você colocou no JSON. No modelo, um campo pode ser lido como fala; nos arguments de um tool, como uma ação.

Tool Calling: abuso de privilégio que parece uma chamada válida

O perigo não é «o modelo consegue emitir JSON?». É se o Host trata o modelo como um chamador já autorizado. É o clássico confused deputy: o modelo propõe; a permissão pertence à sessão do usuário, ao tenant e à conta de serviço. «Chame export_orders» não é a mesma coisa que «este usuário pode exportar a tabela».

Excesso de privilégio em 2026 costuma parecer isto:

  • Um único run_command / http_request com strings quase arbitrárias;
  • Tools que rodam com conta de serviço mais ampla que o usuário final;
  • O Host checa a sintaxe JSON, mas não «este usuário pode mexer neste recurso?»;
  • JSON do usuário ou da página passa direto como arguments do tool, pulando o seu Schema.

Uma declaração aberta demais, e depois uma que você de fato consegue auditar:

{
  "name": "fetch_url",
  "description": "Fetch any URL and return text.",
  "parameters": {
    "type": "object",
    "properties": {
      "url": { "type": "string" }
    },
    "required": ["url"]
  }
}
{
  "name": "fetch_public_doc",
  "description": "Fetch an allowlisted documentation URL.",
  "parameters": {
    "type": "object",
    "properties": {
      "doc_id": {
        "type": "string",
        "pattern": "^doc-[a-z0-9-]{1,32}$"
      }
    },
    "required": ["doc_id"],
    "additionalProperties": false
  }
}

A segunda não é «mais inteligente». Ela vira uma capacidade aberta num identificador auditável. O Host resolve doc_id contra a própria allowlist, completa a origem e aplica timeouts e tetos de tamanho. O modelo nunca vê uma URL arbitrária, então sobra um caminho a menos até uma fonte não confiável.

Antes de executar, três perguntas: este tool está na allowlist da sessão? Os arguments passaram um Schema e uma checagem de autorização independentes do modelo? Na falha, você recusa — ou devolve o detalhe do erro como texto fresco injetável? Erros de parâmetros e validação cobre o pipeline. Em segurança, acrescente: falhe fechado, e não deixe o modelo retentar com arguments novos num loop sem teto.

Um Schema frouxo demais é uma vulnerabilidade

Structured Output e Tool Calling usam os dois JSON Schema para bloquear tokens ilegais — o contrato padrão de 2026, veja o que é Structured Output. Schema garante forma, não sentido seguro.

Contratos frouxos costumam parecer isto:

  • type: object sem properties, ou additionalProperties deixado em true;
  • Um nome de ação que deveria ser enum, escrito como qualquer string;
  • Um campo identificador que cabe um brief de uma página;
  • O modo strict do fornecedor cobre um subconjunto, e o Host assume que «o lado do modelo já bloqueou tudo».

Empilhe duas portas: o Schema do lado do modelo reduz disparates; o Host valida de novo com o mesmo Schema (ou um mais estrito) e projeta para um tipo interno. Structured Outputs do fornecedor não substituem a sua autorização. Diferenças de campo entre fornecedores já são risco — veja a comparação de Structured Output. Uma migração que afrouxa o contrato alarga a superfície de ataque.

Quando Schema é controle de segurança, prefira enum, const, pattern, maxLength, minimum / maximum, required, additionalProperties: false. Se você precisa de texto livre, isole, coloque teto e nunca mapeie texto livre para nome de tool nem para URL.

MCP e ferramentas externas: onde fica o limite de confiança

MCP resolve descoberta e invocação entre processos. Não resolve «este Server é benigno?». O que é MCP já traça a linha JSON-RPC: API do modelo de um lado, Host ↔ Server do outro. Segurança precisa de uma segunda linha: descrições de tools, texto de Resources e JSON de retorno de um MCP Server de terceiros são entrada não confiável.

O risco não é a data do protocolo. É confiança colocada na camada errada:

  • description / inputSchema de tools/list colados no system prompt;
  • result de tools/call entrando no turno seguinte sem validação;
  • Servidores demais num Host, nomes ou capacidades sobrepostos, e executa mesmo assim;
  • Tratar «o usuário permitiu este Server» como «cada frase que ele devolve é uma instrução».

Skills têm o mesmo tipo de problema: um Skill é um how-to, não uma camada de autorização. Carregar SKILL.md de um repositório não confiável acrescenta um fluxo que o modelo vai seguir. Veja o que as quatro camadas controlam — não troque Schema por um Skill, nem isolamento de permissão por um Subagent. Um Subagent pode estreitar tools; o pai ainda decide quais segredos ele recebe.

Lista de defesa: validar, allowlist, menor privilégio

Aperte ao longo do caminho de execução, de fora para dentro — não comece pelo prompt:

  1. Escreva o contrato primeiro. Um JSON Schema por tool: required completo, additionalProperties: false, identificadores via pattern / enum.
  2. Valide de novo no Host. Não confie em «o modelo já seguiu o Schema». Use ajv ou equivalente; falhe fechado.
  3. Projete; não repasse. Copie campos da allowlist para um DTO interno e só então chame a API de negócio.
  4. Minimize tools. Prefira fetch_public_doc a fetch_url; prefira só leitura a escrita.
  5. Autorize como o usuário, não como o modelo. Cheque sessão, tenant e ACL do recurso dentro da implementação do tool.
  6. Ponha portão em efeitos colaterais de saída. Enviar, transferir, apagar, deploy em produção: motor de política ou confirmação humana.
  7. Rebaixe texto externo. Resultados de tools, páginas e e-mail não são system. Quando precisar, um Subagent só leitura devolve um resumo ao pai.
  8. Audite arguments. Registre nome do tool, parâmetros validados, quem autorizou, se foi negado.

Prompts ainda ajudam: diga que dados não são instruções, liste ações proibidas. São camada de apoio. Dez frases a mais de prompt não substituem um Schema ou um ACL que faltam.

Revisar contratos com ferramentas JSON locais

Antes de publicar, olhe três arquivos juntos: os parameters do tool / o inputSchema de MCP, um objeto de arguments do «caminho feliz», e uma amostra de propósito feia (chaves a mais, tipos errados, strings enormes). Sem segredos reais, sem dados reais de usuário.

  • Validador JSON — a amostra é JSON legal, e satisfaz o seu Schema?
  • JSON Diff — o que o modelo acrescentou além do objeto mínimo?
  • Visualizador de árvore — o aninhamento está mais fundo do que deveria; um campo string esconde outra estrutura?

Nada sai do navegador. Serve para revisar um contrato prestes a ir para produção, e para comparar arguments de uma chamada que falhou. Estabilize nomes de campo e required, depois ligue o Host ou o MCP Server.

FAQ

JSON Schema consegue impedir Prompt Injection?

Não sozinho. Schema limita a forma e o intervalo dos arguments de um tool, e reduz a chance de uma string arbitrária virar uma ação arbitrária. Injeção indireta acontece quando o texto entra no contexto — você ainda precisa de procedência, rebaixamento e portões de alto impacto.

O modelo já usa Structured Output / modo strict. O Host ainda precisa validar?

Sim. As restrições do fornecedor valem na geração, e cada um suporta um subconjunto diferente de Schema. Decisões de segurança precisam acontecer antes da execução, com o seu validador e a sua autorização.

Qual o problema de passar o JSON do usuário direto para um tool?

Você pula o contrato. Campos a mais, confusão de tipos e texto aninhado chegam iguais em código com efeito colateral. Valide, depois projete; passe só campos da allowlist.

Dá para usar a saída de um MCP Server como system prompt?

Não. Descrições de tools/list, texto de Resources e resultados de tools/call são dados não confiáveis. Valide e reentre no diálogo com privilégio menor.

Qual é a linha de base mínima viável de segurança de um Agent?

JSON Schema estrito, revalidação no Host, allowlist de tools, autorização por usuário e portões nos efeitos colaterais de saída. Sem essas cinco, não ligue o Agent a dados de produção.

Como eu checo localmente se o JSON de um tool é perigoso?

Coloque Schema e amostras de arguments na caixa de ferramentas JSON para validação e Diff. Confirme required, additionalProperties e limites de comprimento/enum antes de ligar o Host ou o MCP Server.

Resumo

A superfície de ataque do Agent fica entre dados estruturados e a execução de tools: JSON malicioso fura o contrato, Prompt Injection desvia a intenção, Tool Calling vira intenção em efeito colateral, e um Schema frouxo abre a porta para os três. A pilha padrão de 2026 (Tools, MCP, Skills, Subagents) deixa Agents mais úteis — e faz «enganar uma invocação» valer mais do que «enganar uma resposta».

Defenda de dentro para fora: faça do JSON Schema um contrato duro; o Host valida, projeta e autoriza; mantenha tools pequenos; não trate texto externo como instrução. Prompts ajudam; não são a fronteira. Valide amostras localmente antes de publicar — modelos podem mudar; nomes de campo, required e «quem pode executar» não deveriam.