Carregador de Páginas

O agente mexeu onde não devia: escopo não é redação, é controle de acesso

O agente mexeu onde não devia: escopo não é redação, é controle de acesso

Foto: panumas nikhomkhai via Pexels

Três relatos abertos no repositório do Claude Code, três usuários diferentes, o mesmo defeito. No primeiro, o agente disparou três subagentes em paralelo contra uma regra que dizia NEVER — o usuário rejeitou dois, mas os três já tinham executado, queimando vinte e sete mil tokens antes de qualquer clique. No segundo, o pedido era mexer num script e nos READMEs que o chamam; o agente alterou um arquivo de pentest histórico que ninguém tinha mencionado. No terceiro, uma hora e cem mil tokens de mudanças brilhantes — todas no repositório errado.

Nenhum desses números é meu.

A reação padrão a isso é sempre a mesma: escrever a instrução melhor. Ser mais específico. Delimitar com mais cuidado. Botar em caixa alta.

É aí que eu quero brigar.

Escopo não é um problema de redação. É controle de acesso. Se a única coisa que impede o agente de tocar num arquivo é uma frase educada no contexto, você não tem escopo. Você tem esperança.

O placar: o escopo não vazou por falta de clareza

Vale ancorar isso em relato verificável, porque o primeiro instinto de todo dev é achar que foi ele quem escreveu mal o pedido.

Antes de começar, um aviso de método: os relatos que eu cito aqui são de issues públicas do repositório do Claude Code, e eu deixo o número de cada uma no texto. O meu monorepo, esse é fechado — quando eu descrever uma peça dele, ela serve pro raciocínio, não pro clique. Você não vai ter que confiar em nada que não dê pra reconstruir na sua própria casa.

Chame o primeiro de o caso do arquivo de pentest (issue #41707). A instrução dele era cirúrgica: "only the script and the READMEs that call it." Difícil escrever mais específico que isso. Não tem ambiguidade, não tem interpretação criativa possível. O que aconteceu:

"Modified a timestamped historical penetration test archive (…) that was never mentioned and should never be touched."

E o custo, nas palavras do autor:

"Every wrong interpretation, repo-wide search, and revert consumed billable tokens. The user had no recourse."

Guarde essa última frase: the user had no recourse. Ela reaparece daqui a pouco.

No segundo — o caso do repositório errado (issue #19635) — o mesmo vazamento aparece com o preço na etiqueta. O relato é sobre um colega de trabalho do autor:

"its really frustrating, he spent over an hour and 100,000 tokens to make all of these amazing changes to the wrong repo. thats wasted money."

Repare no adjetivo: amazing changes. O trabalho estava bom. O código provavelmente estava certo. Só estava no lugar errado — e trabalho excelente no repositório errado vale exatamente o mesmo que trabalho ruim: zero, menos o que você pagou por ele. O mesmo autor termina falando em refund e em class action, o que diz menos sobre a viabilidade jurídica da ideia e mais sobre a temperatura de quem paga a conta de um escopo que vazou.

Repare também no que não falhou nesses casos. A instrução do caso do arquivo de pentest não era vaga. Era boa. O escopo vazou mesmo assim, porque a instrução nunca foi um limite — era uma preferência declarada a um sistema que tinha acesso de escrita ao repositório inteiro.

Permissão que chega depois da execução é notificação

O mais preciso do conjunto é o caso dos três agentes (issue #27032). A regra do usuário era explícita:

"NEVER launch multiple agents in parallel without asking permission first. Always state what you plan to launch, the estimated cost, and wait for approval"

O que aconteceu:

"the model launched 3 Task agents simultaneously in a single message without asking. The user rejected 2 of the 3, but all 3 had already started executing, consuming ~27k tokens total."

Leia de novo a ordem dos eventos. O usuário foi consultado. Ele rejeitou. E os três já estavam rodando.

Isso não é um bug de obediência. É um bug de arquitetura, e ele tem nome:

Pedir permissão a um sistema que já agiu não é permissão. É notificação.

Um checkpoint de autorização que roda depois do efeito colateral não autoriza nada — ele te informa. É a diferença entre um segurança na porta e uma câmera no teto. A câmera é ótima para descobrir o que aconteceu; ela nunca impediu nada de acontecer. E o mesmo relato explica por que a frase no seu arquivo perde a disputa:

"the model's built-in system prompts (plan mode instructions, agent launching defaults) took precedence over user-defined CLAUDE.md instructions, even though the CLAUDE.md was read first and its rules are supposed to override defaults."

Sua regra não é lei. É um dos participantes de uma disputa de prioridade que você não arbitra e não vê.

A pergunta certa não é "o que eu pedi" — é "o que ele alcança"

Aqui está a inversão inteira, e ela é emprestada de segurança, não de prompt engineering.

Ninguém protege um sistema pedindo educadamente para o processo não ler /etc/shadow. Você tira a permissão. A pergunta que um profissional de segurança faz nunca é "o que esse processo pretende fazer?" — é "o que esse processo consegue fazer se estiver errado, comprometido ou só confuso?". Isso tem nome: raio de ação. Blast radius. Princípio do menor privilégio.

Agora olhe o seu agente. Ele tem escrita recursiva no repositório todo, shell, rede e credenciais no ambiente. O raio de ação dele é tudo. E a única coisa entre ele e o seu arquivo de pentest de março é um parágrafo em markdown que compete de igual para igual com o system prompt do fabricante.

Trocar esse parágrafo por um parágrafo melhor não muda o raio de ação em um byte.

O que eu fiz: tirar a regra de dentro do modelo

Duas honestidades antes do código. Primeira: essa arquitetura tem dias de estrada — nasceu em 2026-07-14. Segunda, decorrente: eu vou te contar a decisão e o raciocínio, não resultado. Não tem benchmark aqui, não tem percentual de melhora, não tem "reduziu X%". Quem te vende número com essa quilometragem está vendendo outra coisa.

O princípio é simples: se a regra precisa valer, ela não pode morar dentro do modelo. Ela mora no harness, em código, e é injetada a cada turno — não pedida.

Na prática, três peças. Um manifesto (roteador/routes.json) onde cada micro-projeto declara explicitamente que memória, que agentes e que código ele pode tocar — hoje são 12 projetos declarados ali. Um roteador (roteador/route_projetos.py) que casa o pedido contra esse manifesto e monta o bloco do que carregar — anotando a rota escolhida num arquivo de estado, que é como o resto do sistema descobre onde ele deveria estar mexendo. E um validador (roteador/validate_routes.py) que confere se o manifesto está mentindo: cada caminho declarado tem que existir em disco, sob pena de o roteador injetar ponteiro quebrado. O contrato de saída dele é seco — 0 se não houver erro, 1 se houver, e 2 se o próprio validador estourar, porque um validador que falha em silêncio é pior que validador nenhum.

Nada disso depende de o modelo querer colaborar, porque nada disso roda dentro dele. Roda no harness — a ferramenta que embrulha o modelo — através de hooks: código seu, disparado pela ferramenta em momentos fixos do ciclo, antes de o modelo ter chance de opinar.

Os meus entram em dois momentos: quando o prompt é enviado e antes de cada uso de ferramenta. O harness injeta, o modelo recebe. E a peça que interessa a este artigo é a guarda cross-projeto (roteador/route_guard.py): antes de todo Write/Edit/MultiEdit, ela compara o caminho-alvo com a rota vigente e avisa quando você está editando um projeto diferente daquele que está roteado.

Ela avisa. Ela não impede. Isso não é acidente e não é vitória — é um trade-off que eu escolhi e que me custa alguma coisa. Bloquear trava trabalho legítimo (cross-projeto acontece, e é normal), e uma guarda que atrapalha vira uma guarda que alguém desliga. Então ela sinaliza e o dono decide. Todo sys.exit() dela é 0: nunca bloqueia. É uma câmera, não um segurança. Eu escolhi a câmera, sabendo do que estou abrindo mão.

O bastidor de hoje: o meu roteador errou a rota

Enquanto eu escrevia este artigo, o roteador casou a rota errada. O pedido era um artigo para o blog-seo. O roteador mandou para carrossel-ia. Quando a escrita foi salvar o arquivo, a guarda disparou:

"aviso de rota: editando projetos/blog-seo mas a rota atual e projetos/carrossel-ia. Confirme que e intencional (cross-projeto) — nao misture frentes sem o dono pedir."

A causa é boba e está no manifesto: carrossel-ia declara as keywords "ia" e "prompt". Um briefing sobre agentes de IA está cheio das duas. O projeto errado ganhou no placar por usar as palavras mais genéricas do repositório.

Ou seja: o sistema errou, e a guarda pegou o erro do próprio sistema. Não o erro do modelo — o meu. Escrevi o manifesto, escolhi a keyword ruim, e o componente determinístico que eu construí para acertar rota me entregou a rota errada com toda a confiança do mundo.

Guarde a simetria com o caso do repositório errado: lá, uma hora de trabalho excelente no lugar errado. Aqui, o mesmo vazamento — só que alguém avisou antes do Write. É essa a diferença inteira, e ela não veio de uma instrução melhor.

É por isso também que a guarda existe num nível diferente do roteador. Se o mecanismo de escopo e o mecanismo que verifica o escopo forem o mesmo mecanismo, ele erra junto, em silêncio, com as duas assinaturas.

Onde a minha guarda não te salva

Se eu parasse no parágrafo anterior, isto aqui seria propaganda. Então vamos aos furos, que são reais e que eu encontrei lendo o meu próprio código para escrever este texto:

Nada disso invalida o princípio. Reforça: essas quatro falhas são falhas de implementação, e falha de implementação você conserta, mede e testa. Uma frase educada no contexto não tem implementação para consertar.

O que dá para fazer hoje, sem esperar patch

Não afirmo que essas issues serão ou não corrigidas — e é exatamente esse o ponto. O princípio arquitetural sobrevive ao patch, porque ele não depende de o modelo obedecer:

  1. Corte o raio de ação antes de escrever a instrução. Checkout separado, git worktree ou container. O que não está montado não é alterado — nenhuma redação alcança isso. É também a única defesa real contra o caso do repositório errado: ele não estaria montado para ser alterado.
  2. Ponha o veto onde ele roda antes do efeito. Hook de PreToolUse ou regra de deny da própria ferramenta valem mais que dez linhas de NEVER, porque rodam fora do modelo. E cubra as ferramentas todas — o meu furo de Bash é o seu também.
  3. Trate diff como fronteira. Commit pequeno, review do diff, pre-commit hook. É a última barreira que não negocia com o contexto.
  4. Verifique num nível diferente do que executa. Se o verificador for o mesmo mecanismo que age, os dois erram juntos.

Perguntas frequentes

Prompt melhor não resolve nada, então? Resolve — e vale a pena. Boa instrução reduz a frequência do erro. Ela só não muda a consequência dele. Prompt é qualidade; escopo é segurança. Quando os dois brigam, quem tem acesso de escrita ganha.

Por que sua guarda avisa em vez de bloquear? Porque trabalho cross-projeto legítimo existe e trava tudo se eu bloquear — e guarda que atrapalha é guarda que alguém desliga. É uma escolha com custo: ela não impede nada. Se o seu contexto tolera a fricção, bloqueie; é mais forte que o que eu faço aqui.

Isso não é paranoia para um agente que só escreve código? O caso do arquivo de pentest é de um agente que "só" ia mexer num script e num README — e alterou um arquivo histórico que ninguém citou. O caso do repositório errado é de uma hora e cem mil tokens no lugar errado. Ninguém ali escolheu esse raio de ação. Só nunca tinham limitado.

Você tem número provando que sua abordagem é melhor? Não. A arquitetura é de 2026-07-14 e não tem benchmark. O que eu afirmo aqui é decisão e raciocínio, não resultado — inclusive porque o meu próprio roteador errou hoje.

Voltando ao placar

Vinte e sete mil tokens. Três agentes que já tinham executado quando o "não" chegou. Um arquivo de pentest que ninguém mencionou. Uma hora e cem mil tokens no repositório errado.

Nenhum desses casos foi causado por instrução mal escrita — a do arquivo de pentest era ótima, e as mudanças que foram parar no repositório errado eram amazing. Todos foram causados pela mesma coisa: o sistema podia. E o que um sistema pode fazer, ele eventualmente faz, na hora errada, no arquivo errado, com a sua fatura.

The user had no recourse. Recurso é o que você constrói antes, em código, num nível que o modelo não negocia. Depois do Write, não existe recurso — existe git revert e a fatura.