CTO no Integrar Plus • Publicado em 01 de October de 2026
A comparação entre prompt injection e SQL injection virou o lugar-comum mais repetido nas discussões sobre segurança de inteligência artificial aplicada. O problema é que quase todo mundo extrai a lição errada dessa analogia. Quando um desenvolvedor escuta que prompt injection é o novo SQL injection, o primeiro reflexo dele costuma ser procurar o equivalente a uma função de escape, criar uma camada de regex ou colocar uma barreira de filtros para limpar o texto antes de enviá-lo ao modelo.
Esse reflexo custa caro. No SQL injection histórico, a correção definitiva não veio de sanitizar strings com substituições mágicas de aspas. Ela veio quando a indústria abandonou a interpolação de texto e adotou consultas parametrizadas diretamente no protocolo do banco de dados. Em uma query preparada no PostgreSQL, o motor de execução separa a gramática do comando dos bytes do parâmetro em etapas estruturais distintas. O dado nunca vira código porque o compilador da linguagem SQL já terminou seu trabalho quando o valor chega.
Em modelos de linguagem, essa fronteira estrutural inexiste. Todo o contexto (instruções do sistema, histórico de conversa, trechos recuperados de uma busca vetorial e texto enviado pelo usuário final) é concatenado e transformado em uma única sequência de tokens. A arquitetura dos transformadores não possui um registrador de memória separado para comandos e outro para dados passivos. O texto recuperado de um PDF externo compete pela atenção do modelo com o mesmo peso sintático da instrução que define o comportamento do assistente.
Ao observar as reações da comunidade técnica sobre esse paralelo, vejo dois grupos defendendo abordagens opostas. O primeiro insiste em construir firewalls de texto, usando filtros heurísticos ou até mesmo uma segunda chamada de LLM encarregada de inspecionar a entrada e decidir se há intenção maliciosa. Acho essa linha conceitualmente quebrada. Colocar um modelo para julgar se a entrada de outro modelo contém uma injeção cria uma dependência circular evidente: a instrução maliciosa pode conter comandos desenhados especificamente para convencer o próprio avaliador de que aquele texto é inofensivo. Além disso, pesquisas recentes mostram que ataques adaptativos furam essas defesas em mais de noventa por cento das tentativas. Tratar um canal inerentemente probabilístico como barreira de segurança determinística é receita para surpresas desagradáveis.
O segundo grupo adota uma postura pragmática com a qual concordo: já que não temos como impedir estruturalmente que o modelo interprete dados como instruções dentro da janela de contexto, a segurança precisa migrar da atenção do modelo para o raio de destruição da infraestrutura ao redor dele. A fronteira real de segurança nunca é o que o modelo lê, mas o que o modelo consegue executar e para onde ele consegue enviar dados.
Se você assume como premissa de projeto que qualquer entrada de dados não confiável conseguirá, cedo ou tarde, controlar a saída do modelo, o desenho do sistema muda por completo. Em vez de gastar tempo tentando impedir que o modelo leia comandos indesejados, o trabalho do arquiteto passa a ser garantir que, mesmo sob controle total do invasor, o agente não possua ferramentas nem permissões suficientes para causar danos graves.
Na prática de quem opera sistemas web reais com Ruby on Rails, PostgreSQL e jobs em segundo plano, essa mudança de mentalidade se traduz em decisões bem objetivas.
A primeira decisão diz respeito ao isolamento de rede. O ataque clássico de prompt injection não visa apenas fazer o bot falar bobagem no chat; ele tenta exfiltrar dados confidenciais presentes na memória ou no contexto para um servidor externo controlado pelo invasor. Se o processo que executa as chamadas do modelo ou as ferramentas do agente não tem rota de saída irrestrita para a internet, a exfiltração morre na camada de rede do sistema operacional. No Linux, isso significa usar namespaces de rede, regras estritas de firewall no host ou perfis do sistema que impeçam conexões para qualquer endereço que não seja a API do provedor do modelo ou serviços internos pré-autorizados. Um modelo convencido a enviar seu banco de dados para um endpoint externo falha silenciosamente se o socket de rede nem sequer abre.
A segunda decisão envolve a segregação de credenciais nas ferramentas disponíveis. O erro mais comum que encontro em implementações de agentes é passar para o modelo uma interface de execução genérica ou fornecer credenciais de banco de dados com permissão de leitura ampla. Se uma ferramenta de consulta SQL automatizada roda com o mesmo usuário de aplicação que lê todas as tabelas do sistema, uma injeção bem-sucedida transforma o agente em um cliente de extração de dados da base inteira. Ferramentas expostas a modelos precisam seguir o princípio do menor privilégio levado ao extremo: views dedicadas no PostgreSQL contendo apenas as colunas públicas do tenant atual, conexões somente leitura e limites rígidos de paginação impostos pelo código Ruby, nunca pelo parâmetro gerado pelo modelo.
A terceira decisão é a separação de papéis entre contextos de privilégio, muitas vezes chamada de arquitetura de duplo modelo. Em vez de pedir para um único agente ler documentos externos, interpretar instruções de usuários e tomar ações no banco de dados, você divide o fluxo em duas etapas isoladas. Um modelo sem privilégio algum, sem ferramentas e sem saída de rede, recebe os documentos brutos ou o texto livre do usuário e tem como única tarefa extrair dados estruturados em formato fixo, como JSON validado por um schema rígido. O modelo privilegiado, que realmente tem acesso a chamadas de sistema ou ferramentas internas, nunca lê o texto cru original; ele consome apenas os campos validados que passaram pela checagem de tipos da aplicação. Essa barreira de dados estruturados devolve à aplicação tradicional o controle sobre o que é parâmetro e o que é instrução.
Existe também um debate recente sobre auditoria. Alguns engenheiros defendem que, como o modelo pode ser comprometido a qualquer momento, seus próprios logs internos deixam de ser confiáveis para entender incidentes. Isso é verdade. Se o modelo foi instruído a omitir uma ação ou adulterar o resumo de sua execução, o log gerado pela própria LLM vira evidência corrompida. O registro auditável precisa acontecer na borda da aplicação: toda chamada HTTP disparada pelo worker, toda query enviada ao PostgreSQL e todo payload recebido de uma tool devem ser gravados de forma append-only pelo código do servidor, fora do alcance de qualquer parâmetro editável pelo modelo.
Nenhum desenvolvedor experiente confia na entrada do usuário para montar comandos de shell usando concatenação direta; nós usamos bibliotecas que passam vetores de argumentos imutáveis para o kernel. Enquanto a engenharia de modelos de linguagem não tiver um padrão equivalente de parametrização no nível dos pesos ou dos tokens, tentar resolver prompt injection com instruções no system prompt é o equivalente contemporâneo a tentar bloquear SQL injection substituindo aspas por espaços vazios.
A única postura sustentável para quem coloca agentes em produção é assumir que o texto que você não controla vai burlar a atenção do modelo. Quando você aceita essa fragilidade como uma característica do componente e não como um bug corrigível por texto, a defesa volta para onde nunca deveria ter saído: isolamento de privilégios de banco, contenção de tráfego de rede e validação estrita de dados no código que você mesmo compila e controla.
CTO e arquiteto full stack especializado em Ruby on Rails, Vue.js, sistemas multi-tenant e infraestrutura distribuída.