Prompt injection em agentes de programação
Veja como texto controlado por atacantes entra no agente no servidor, quais ferramentas ampliam o risco e quais defesas reduzem o impacto de forma realista.
O que é prompt injection contra um agente de programação
Prompt injection contra um agente de programação pode ser descrito de forma simples: o texto que o agente lê é tratado como uma instrução que o agente segue. O agente abre um ficheiro, um comentário numa pull request, uma página web ou o resultado de uma chamada de ferramenta. Tudo chega como o mesmo tipo de texto que o seu próprio pedido. Se um atacante controlar qualquer parte desse texto, estará a escrever na sua sessão.
Todos os produtos de agentes atuais têm esta propriedade. O modelo recebe uma sequência de tokens. O seu pedido, o system prompt, o conteúdo dos ficheiros e os resultados das ferramentas são unidos, e o modelo prevê o que vem a seguir. Não existe um bit de privilégio num token. O formato não indica que parte foi autorizada por si e que parte veio do README de um desconhecido.
Esta página define o modelo de ameaça: como o texto controlado pelo atacante chega a um agente em execução num servidor, o que o atacante obtém em cada ponto e quais as defesas que justificam o esforço. As recomendações de contenção nos nossos outros guias só fazem sentido depois de saber de que está a conter o agente.
Por que o modelo não consegue separar conteúdo de instruções
O treino ajuda, mas não resolve o problema. Os modelos atuais são treinados para tratar o texto recuperado com suspeita e recusam muitas tentativas rudimentares. Uma recusa é uma probabilidade, não uma regra. Um atacante pode reformular, tentar novamente e ocultar o texto num formato que ninguém tenha previsto. Nada limita o número de formulações que pode experimentar.
O projeto OWASP GenAI acompanha este problema como LLM01:2025 Prompt Injection e divide-o em dois tipos. A injeção direta é o prompt do próprio utilizador a alterar o comportamento do modelo. A injeção indireta é conteúdo externo, como um site ou um ficheiro, a alterar o comportamento quando o modelo o processa. A injeção indireta é a que importa num servidor, porque um agente lê muito mais texto do que aquele que escreve.
O primeiro estudo sistemático é o de Greshake e colaboradores, Not what you've signed up for (2023). A conclusão que deve ser retida é esta: quando uma aplicação fornece texto recuperado a um modelo que pode chamar ferramentas, o processamento desse texto aproxima-se da execução arbitrária de código.
A condição que transforma uma leitura numa violação
Ler texto hostil não causa danos por si só. Os danos precisam de um caminho para sair da máquina.
Simon Willison chamou a essa combinação a tríade letal em junho de 2025. Um agente que tem dados privados, está exposto a conteúdo não fiável e pode enviar dados para o exterior pode ser induzido a retirar os primeiros através do terceiro.
Um agente de programação no seu VPS tem tudo isso desde o primeiro dia. Os dados privados são o seu código-fonte, o seu ficheiro .env, as suas chaves SSH e o seu histórico da shell. O conteúdo não fiável é cada repositório, página e resultado de ferramenta que lê. A saída pode ser git push, curl, npm publish, o corpo de um pull request ou uma ligação apresentada no terminal em que clica.
Não pode remover a segunda condição, porque ler texto não fiável é a função para a qual contratou o agente. Por isso, todas as defesas práticas atuam sobre as outras duas.
Onde texto não confiável chega a um agente de programação num servidor
O repositório no qual o agente está a trabalhar
Todos os ficheiros no checkout são entrada. Comentários no código-fonte, README.md, changelogs, fixtures de testes, código incorporado de terceiros e os próprios ficheiros de instruções do agente: CLAUDE.md, AGENTS.md e os equivalentes. Um agente a quem é pedido que compreenda uma base de código lê esses ficheiros, porque foi isso que lhe pediu.
O que um atacante obtém aqui é alcance sobre todas as pessoas que clonam o repositório e apontam um agente para ele. Os ficheiros de instruções são a via mais direta, porque existem para serem lidos como instruções. Um pull request que adiciona quatro linhas úteis a CLAUDE.md e uma linha que redireciona o agente é uma alteração que um revisor humano pode ignorar numa leitura rápida.
Issues, pull requests e comentários de revisão de código
Tudo o que um desconhecido pode escrever no seu sistema de tracking chega ao agente assim que lhe pede para fazer a triagem. Em May 2025, a Invariant Labs publicou uma descoberta sobre GitHub MCP exatamente com este formato. O agente de um developer tinha acesso a um repositório público e a repositórios privados. Um atacante abriu uma issue no repositório público. Quando o developer pediu ao agente que consultasse as issues abertas, o agente leu conteúdos de repositórios privados e escreveu-os num pull request do lado público.
Esse relatório descreve um problema arquitetural, não um defeito de código no servidor MCP. O agente tinha um token de acesso amplo, lia de uma caixa de entrada pública e tinha permissão para escrever. Nada estava mal configurado no sentido habitual. Por isso, a resposta é definir o âmbito de acesso, não aplicar um patch.
Páginas Web que o agente consulta
Documentação, respostas em fóruns, uma página de um fornecedor ou um resultado de pesquisa. Qualquer um destes conteúdos pode incluir texto escrito para o agente, e não para si. O HTML convertido em texto oferece espaço adicional a um atacante, porque conteúdo que um browser nunca apresenta continua a chegar ao modelo.
Um atacante obtém controlo no momento em que o agente é menos monitorizado. Ninguém lê o texto completo de uma página que o agente consultou enquanto procurava uma informação.
Saída de ferramentas MCP
MCP (model context protocol) é a forma comum de os agentes se ligarem a ferramentas externas. Os resultados regressam como texto e entram diretamente na janela de contexto. Existem duas superfícies, não uma. Os dados devolvidos por uma ferramenta são a superfície óbvia. O nome e a descrição da própria ferramenta, que o modelo lê para decidir quando a chamar, são a outra. Um servidor que não controla pode alterar qualquer uma delas entre chamadas.
Um atacante que introduza texto na saída de uma ferramenta chega a todas as outras ferramentas que o agente tem. É assim que uma injeção numa função de baixo valor acaba por controlar uma função de alto valor.
Logs de CI, saída de compilação e metadados de dependências
npm install imprime texto de pacotes que não escreveu. Uma falha de teste imprime uma mensagem de asserção de uma biblioteca. O log de um job de integração contínua (CI) tem milhares de linhas de saída de terceiros. Se pedir a um agente para corrigir uma compilação com falhas, ele lê tudo isso.
Aqui, um atacante obtém acesso à máquina de compilação. Normalmente, essa máquina contém credenciais de deployment e tokens de registry, e recebe menos atenção do que um laptop.
O que um atacante realmente obtém
Vale a pena planear quatro resultados.
Roubo de credenciais. Tudo o que o processo do agente consegue ler está em risco: variáveis de ambiente, ~/.aws/credentials, ~/.ssh, um token gh, um ficheiro de configuração do Docker. O envio desses dados para fora não requer curl. Um commit para um branch, a descrição de um pull request, um pacote publicado num registry ou uma consulta DNS para um nome controlado pelo atacante transferem dados para fora do servidor.
Alterações de código que aprova. Escrever código é a função do agente, por isso fazê-lo escrever uma linha subtilmente incorreta é o resultado mais fácil de obter. Pode ser uma dependência adicionada ou uma chamada de logging que coloca um token num log que envia para outro local.
Persistência. Um ficheiro escrito uma vez continua a funcionar sem qualquer modelo envolvido: um hook em .git/hooks, um script postinstall em package.json, uma linha acrescentada a um ficheiro de arranque da shell ou uma linha adicional em CLAUDE.md. O comando seguinte executa-o.
Movimentação dentro da sua rede. O agente é executado no local onde o instala. Se esse servidor conseguir aceder a uma base de dados em loopback, a um serviço de administração interno, ao serviço de metadados do seu fornecedor de cloud ou a outro host na rede privada, qualquer elemento que controle o agente também conseguirá.
Os modos de aprovação automática removem a última verificação
No modo predefinido, Claude Code pede confirmação antes de executar um comando ou editar um ficheiro. Esse pedido é a verificação humana entre cada superfície acima e uma ação real. Os modos que removem o pedido removem essa verificação.
A documentação é clara sobre bypassPermissions: use-o apenas em ambientes isolados, como contentores ou máquinas virtuais, onde Claude Code não possa causar danos. O modo automático é menos restritivo e aprova automaticamente as chamadas de ferramentas, com verificações de segurança em segundo plano que confirmam se as ações estão alinhadas com o seu pedido. Essas verificações detetam muitos problemas. Continuam a depender do julgamento do modelo sobre a saída do modelo, por isso devem ser tratadas como um filtro e não como uma barreira.
Um administrador pode remover ambas as verificações. Defina permissions.disableBypassPermissionsMode ou permissions.disableAutoMode como "disable" num ficheiro de definições e coloque esse ficheiro nas definições geridas, para impedir que um projeto obtido por checkout o substitua. O nosso guia sobre o modo automático e as regras de permissões do Claude Code explica onde cada regra produz efeito.
Defesas, organizadas pelo que oferecem
Nenhuma destas medidas é uma correção completa. Cada uma limita o que o agente pode guardar ou o que pode fazer com isso.
- Uma máquina que possa destruir e reconstruir, para que uma intrusão lhe custe uma hora em vez de um incidente.
- Credenciais separadas das suas, limitadas a um repositório e com validade curta.
- Nenhum segredo de longa duração no ambiente que os comandos do agente herdam.
- Aplicação de regras do sistema operativo ao tráfego de saída e ao acesso a ficheiros, válida para todos os processos iniciados pelo agente.
- Pedidos de aprovação ativados para escritas e chamadas de rede.
- Hooks como controlo determinístico para as ações específicas que consegue identificar.
- Ler o diff antes de o integrar.
A ordem é importante. Os itens 1 a 4 continuam válidos mesmo quando o modelo está totalmente sob controlo de um atacante. Os itens 5 a 7 dependem de alguém prestar atenção, que é precisamente o que deixa de acontecer durante uma execução longa do agente.
Coloque o agente numa máquina descartável
Um VPS que contém um checkout e um token limitado é um alvo muito menor do que um portátil com as suas chaves. Execute o agente com um utilizador próprio sem privilégios, não com a sua conta de login e não como root. Os nossos guias sobre uma VM descartável para agentes de programação e utilizadores com privilégios mínimos num VPS explicam a configuração, e executar o Claude Code com segurança num VPS explica a utilização diária.
Retire os segredos do ambiente
Uma variável de ambiente pode ser lida por todos os processos filhos, o que inclui todos os comandos executados pelo agente. A sandbox do Claude Code pode remover variáveis identificadas antes de cada comando executado na sandbox. No Linux, a sandbox precisa primeiro de dois pacotes:
sudo apt-get install bubblewrap socatDepois, em ~/.claude/settings.json:
{
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["github.com", "*.npmjs.org"]
},
"credentials": {
"envVars": [
{ "name": "GITHUB_TOKEN", "mode": "deny" },
{ "name": "NPM_TOKEN", "mode": "deny" }
]
}
}
}Uma entrada deny remove essa variável antes de cada comando executado na sandbox, e allowedDomains contém os comandos executados na sandbox para os hosts indicados. O bloco credentials requer o Claude Code v2.1.187 ou posterior, verificado em agosto de 2026. Execute /sandbox numa sessão para ver quais as camadas ativas e quais as dependências em falta. Decidir quais os segredos que precisam sequer de existir nessa máquina é a parte mais importante do trabalho, e manter os segredos fora do alcance de um agente de IA aborda esse tema.
Corte o tráfego de saída no sistema operativo
Uma regra de firewall não depende do que o modelo decidiu. Execute o agente com um utilizador agent dedicado e descarte o tráfego enviado por esse utilizador:
table inet agentcage {
chain output {
type filter hook output priority filter; policy accept;
meta skuid "agent" ct state established,related accept
meta skuid "agent" oif lo accept
meta skuid "agent" counter drop
}
}Assim, o utilizador agent fica limitado ao loopback, pelo que o tráfego tem de passar por um proxy executado na mesma máquina, e o proxy mantém a lista de permissões de hosts. Com https_proxy apontado para esse proxy, o cliente envia um pedido CONNECT e o proxy faz a resolução do nome, pelo que o agente não precisa do seu próprio DNS de saída (domain name system). Verifique o resultado com sudo nft list ruleset e monitorize o contador da regra de descarte enquanto o agente tenta alcançar algo novo.
Mantenha uma segunda sessão SSH aberta enquanto aplica alterações à firewall. Verifique também o comportamento do runtime de contentores em relação a estas regras: o Docker escreve as suas próprias chains, e as portas Docker publicadas contornam o ufw descreve a surpresa que isso provoca.
Hooks: a verificação que o modelo não consegue contornar
As regras de permissões e os hooks são aplicados pelo Claude Code, não pelo modelo. A documentação afirma-o claramente: as instruções no prompt ou em CLAUDE.md influenciam o que o Claude tenta fazer, mas não alteram o que o Claude Code permite. Essa distinção é o valor principal. Uma linha em CLAUDE.md com o texto "nunca execute curl" é uma sugestão que um parágrafo injetado pode contestar. Um hook é um processo que devolve um código de saída.
Registe um hook PreToolUse em .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/no-egress.sh"
}
]
}
]
}
}O hook recebe a chamada da ferramenta como JSON na entrada padrão. O código de saída 2 bloqueia a chamada e mostra ao Claude o motivo recebido na saída de erro padrão. O código de saída 0 permite que a chamada continue pelo fluxo normal de permissões.
#!/usr/bin/env bash
# PreToolUse: stdin holds the tool call, exit 2 blocks it.
cmd=$(jq -r '.tool_input.command // ""')
if printf '%s' "$cmd" | grep -qE '(^|[;&|]|\s)(curl|wget|nc|ncat)(\s|$)'; then
echo "Blocked: this repository does not allow outbound network commands." >&2
exit 2
fi
exit 0Agora, a parte importante. Isto é uma denylist aplicada a uma string de shell, e denylists aplicadas a strings de shell têm falhas. python3 -c abre um socket sem utilizar a palavra curl. Um destino make deploy oculta a mesma chamada num nível adicional. Escreva hooks para os erros que consegue identificar e coloque o limite em que realmente confia no kernel ou na rede.
As regras de negação de permissões têm uma limitação de correspondência que importa conhecer. As regras de negação Read e Edit abrangem as próprias ferramentas de ficheiros do Claude e os comandos de ficheiros que ele reconhece no Bash, como cat, head, tail e sed. Não abrangem um script Python ou Node que abra o ficheiro diretamente. As regras são avaliadas primeiro por negação, depois por pedido e, por fim, por permissão, pelo que uma regra de negação não pode ter uma exceção numa lista de permissões.
{
"permissions": {
"deny": [
"Read(.env)",
"Read(./secrets/**)",
"Bash(git push *)"
]
}
}Monitorize o que sai e leia o diff
Uma execução do agente produz um diff e um conjunto de chamadas de rede. Ambos devem ser analisados antes de qualquer integração ou deployment. Uma revisão de segurança self-hosted do diff deteta uma classe de alterações diferente da análise rápida de uma pessoa, e saber o que um agente de programação envia para fora da máquina permite conhecer o tráfego normal, para que um pedido anómalo se destaque.
O que continua por resolver
Ainda não existe uma separação fiável entre conteúdo e instruções. Todas as defesas disponíveis são um filtro com uma taxa de falhas ou limitam as consequências. Nenhum componente da pilha marca um trecho de texto como dados que nunca devem ser obedecidos.
Os filtros ajudam, mas também falham. Um classificador que deteta a maioria das tentativas de injeção continua a ter de acertar sempre, enquanto um atacante só precisa de acertar uma vez. Essa assimetria explica por que uma taxa de sucesso publicada para uma defesa é um ponto de partida para a tentativa seguinte, e não uma garantia.
O trabalho mais promissor está ao nível do design, e não ao nível do modelo. O CaMeL, de Defeating Prompt Injections by Design (Debenedetti e colegas, 2025), extrai primeiro o fluxo de controlo e o fluxo de dados do pedido fiável, para impedir que dados não fiáveis alterem o comportamento do programa. Depois, aplica verificações de capacidades quando as ferramentas são chamadas. Os próprios valores apresentados no artigo para o benchmark AgentDojo mostram o custo dessa abordagem.
The data behind this chart
[
{
"label": "Undefended agent",
"tasks_solved_pct": 84
},
{
"label": "CaMeL",
"tasks_solved_pct": 77
}
]O agente sem proteção resolveu 84 por cento das tarefas. O CaMeL resolveu 77 por cento, com uma garantia de segurança associada. Estes são os valores publicados no artigo para um benchmark e não uma medição da sua carga de trabalho. A diferença entre ambos representa aproximadamente o custo de uma garantia real atualmente.
Até um design desse tipo estar disponível nas ferramentas que utiliza diariamente, parta do princípio de que o agente será comprometido a dada altura e torne esse evento pouco relevante. Esse é o fundamento para utilizar uma máquina descartável, credenciais com âmbito limitado, saída de rede controlada e o hábito de ler o diff.
FAQ
Posso impedir a injeção de prompts dizendo ao agente para ignorar instruções nos ficheiros?
Não. Essa frase está na mesma janela de contexto que o ataque e compete com o texto do atacante em condições de igualdade. A documentação do Claude Code estabelece claramente a distinção: as instruções no seu prompt ou CLAUDE.md definem o que o agente tenta fazer, mas não alteram o que a ferramenta permite. Trate um ficheiro de instruções como uma declaração de intenção e coloque tudo aquilo em que baseia a segurança em regras de permissões, num hook PreToolUse ou numa regra de firewall.
A injeção de prompts representa um risco real se o agente apenas aceder ao meu próprio repositório?
Sim, porque o seu repositório está cheio de texto que não foi escrito por si. Ficheiros README de dependências, URLs em lockfiles, fixtures de testes, código incluído no repositório e a saída de npm install são obtidos durante uma tarefa normal. Tudo o que for obtido de um sistema de acompanhamento de issues ou de um site de documentação chega da mesma forma. O risco aumenta com a quantidade de conteúdo que o agente lê, e um agente útil lê muito.
Executar o agente num container resolve o problema?
Limita os danos, mas apenas se também remover as credenciais. Um container com o seu SSH agent encaminhado, credenciais cloud no ambiente e acesso de rede sem restrições entrega a um atacante quase tudo o que o host teria. O que o container realmente oferece é um sistema de ficheiros que pode eliminar e um local limpo para aplicar regras de tráfego de saída. Combine-o com um token limitado a um único repositório.
Que alteração única reduz mais o risco?
Remova as credenciais de longa duração do ambiente herdado pelos comandos do agente e, em seguida, aplique nessa máquina uma política de tráfego de saída com negação por defeito. Em conjunto, estas medidas eliminam a terceira condição da tríade letal: o texto ainda pode controlar o agente, mas os dados a que este consegue aceder não têm para onde ir de forma útil. Os pedidos de aprovação e a revisão dos diffs também ajudam, mas dependem de uma pessoa se manter atenta durante uma execução longa. Por isso, ficam abaixo dessas duas alterações.