← Voltar para todas as publicações
Publicado em · por Renaud Deraison

A regra de permissão guardou a senha

Em 6 de novembro, a Apiiro relatou que o botão “Yes, and don't ask again” do Claude Code salva o comando aprovado, segredos incluídos, em .claude/settings.local.json, e que 1.122 repositórios públicos do GitHub tinham feito commit desse arquivo com senhas, tokens e chaves com cara de válidos. Dois scanners de segredos populares deixaram passar 215 deles. No Bromure Agentic Coding, todo segredo que o agente consegue digitar é um substituto, então uma regra salva guarda um valor que não funciona em lugar nenhum, e todo push espera por você no Mac.

Você clica em “Yes, and don't ask again” para uma pergunta não voltar mais. O Claude Code guarda o comando inteiro para reconhecê-lo da próxima vez, senha incluída, num arquivo dentro do seu projeto.

Em 6 de novembro, a Apiiro publicou uma pesquisa sobre agentes de código que vazam segredos pelo “don't ask again”. O The Hacker News noticiou na manhã seguinte.

O mecanismo é banal. Quando o Claude Code pede para rodar um comando de shell e você escolhe Yes, and don't ask again, ele grava uma regra de permissão em .claude/settings.local.json, no projeto. Às vezes a regra é um padrão como npm test *. Às vezes é o comando exato, e quando esse comando tinha uma senha depois de PGPASSWORD=, sshpass -p ou mysql -p, a senha agora está num arquivo JSON ao lado do seu código.

A Apiiro procurou esse arquivo no GitHub público e encontrou:

  • 104.185 cópias em 103.617 repositórios;
  • 1.122 cópias com pelo menos um segredo, 1.379 segredos distintos no total;
  • 379 senhas de bancos de dados, 43 logins SSH de servidores públicos, 42 tokens do GitHub, GitLab e npm, e 23 chaves da AWS;
  • segredos de 63 organizações, incluindo uma chave de nuvem com nível de administrador de um fornecedor de bancos de dados e a senha de proprietário de um banco de prontuários médicos.

Todos esses arquivos ainda estavam no branch padrão quando a Apiiro verificou. Em metade dos repositórios, estavam lá havia pelo menos 217 dias. Commits mais antigos guardavam mais 734 segredos em 509 repositórios, e 211 forks de 74 repositórios carregavam cópias. Somando o .claude/settings.json compartilhado, o histórico e os logs de chat de agentes salvos, a Apiiro contou 2.539 segredos em 1.994 repositórios.

Um clique, quatro passos até um segredo públicoPedido de aprovaçãoPGPASSWORD=s3cr3t psql-h db.example.com …"Yes, and don't ask again"Regra de permissão salva.claude/settings.local.jsono comando inteiro, às clarasNo commit539 / 1.105repos rastreados em que umcommit com o Claude o alterouPúblico no GitHub1.122cópias com segredos;metade há 217 dias ou maisApagar o arquivo não apaga o segredomais 734 segredos em commits antigos de 509 repositórios; 211 forks de 74 repositórios têm os mesmos valores
Como uma senha vai de um pedido de aprovação até um repositório público. A regra de permissão guarda o comando exato. O agente faz commit da pasta do projeto, o arquivo vai junto, e cada fork e cada commit antigo mantém uma cópia depois que o branch padrão é limpo.

Por que passou por tudo

O Claude Code tenta, sim, manter o arquivo fora do git. Na primeira vez que grava settings.local.json, ele adiciona o arquivo às suas exclusões globais do git. Essa regra vive em um único computador. O notebook de um colega, um runner de CI ou um clone novo não a têm, e ela não faz nada por um arquivo que já foi commitado. O Claude Code nunca adiciona o arquivo ao .gitignore do próprio projeto. Dos 16 agentes de código que a Apiiro comparou, o Claude Code é o único confirmado que salva por padrão o comando aprovado completo num arquivo do projeto.

Os scanners de segredos não se saíram melhor. A Apiiro rodou o gitleaks e o TruffleHog em cada cópia. O gitleaks apontou 564 dos 1.122 arquivos, o TruffleHog 643, e 215 escaparam dos dois. A maioria desses 215 eram senhas digitadas em comandos. Os scanners procuram formatos de chave conhecidos como AKIA… ou ghp_…, e sshpass -p hunter2 não bate com nenhum deles.

Usuários relataram o problema há mais de um ano. Uma issue de agosto de 2025, anthropics/claude-code#5544, dizia que o Claude Code guardava nesse arquivo, palavra por palavra, comandos aprovados com chaves de API. Um bot a fechou como “not planned” depois de 60 dias sem atividade. Outros quatro relatos relacionados tiveram o mesmo destino com o mesmo bot, o mais recente na véspera do artigo do The Hacker News.

O segredo estava no comando porque estava nas mãos do agente

Cada um desses 1.379 segredos era um valor que o agente conseguia digitar. Um desenvolvedor o colou no chat, ou deixou num arquivo .env, ou o agente o leu de ~/.aws/credentials ou ~/.pgpass e o embutiu no comando que queria rodar. A regra de permissão anotou o que o agente já tinha.

O conselho da Apiiro para o futuro vai na mesma direção: manter segredos fora dos comandos e referenciar uma variável de um gerenciador de segredos, para que a regra salva contenha só $DB_URL. Isso ajuda se todo desenvolvedor e todo agente seguir a regra todas as vezes. Um agente tentando fazer um comando com erro funcionar vai expandir a variável. O Bromure vai pelo outro caminho e mantém o valor real totalmente fora do ambiente do agente.

O mesmo clique, no Bromure Agentic Coding

O Bromure Agentic Coding roda Claude Code, Codex, Grok, Kimi e omp em VMs Ubuntu no seu Mac. O agente recebe credenciais pelo que chamamos de fronteira do fio: dentro da VM, todo segredo é um substituto, e o valor real fica criptografado no Mac, com a chave mestra no Keychain do macOS. Um proxy no Mac troca o substituto pelo valor real no fio, e só numa requisição para o host ao qual aquela credencial pertence.

Então, quando o agente pede para rodar um comando com um token e você escolhe Yes, and don't ask again, o Claude Code salva o comando exatamente como antes. O token dentro dele é o substituto.

Todo segredo que o agente consegue digitar é um substituto

Os substitutos mantêm o formato que as ferramentas esperam, então nada muda no projeto:

O que a Apiiro encontrou vazadoO que o agente tem numa máquina Bromure
Tokens do GitHub e do GitLabghp_ + 36 caracteres, glpat- + 20, em GH_TOKEN, GITLAB_TOKEN e ~/.git-credentials
Tokens do npmum valor brm_… em NPM_TOKEN, de uma entrada Other API key restrita a registry.npmjs.org
Chaves da AWSo ID real da chave de acesso com uma chave secreta falsa; o Mac reassina cada requisição com a real
Senhas de bancos de dadosum valor brm-db-… na variável que você escolher, para ClickHouse, a Mongo Data API e o Elasticsearch
Logins SSHnenhuma senha: assinaturas feitas por chaves que ficam no Mac

Pegue um comando que um agente poderia rodar contra um endpoint do ClickHouse:

curl -u default:brm-db-7Qk2…Xw9 \
  'https://analytics.example.com:8443/?query=SELECT+count()+FROM+events'

Aprove para sempre e a regra de permissão guarda brm-db-7Qk2…Xw9. O proxy troca o substituto nas requisições para analytics.example.com, no cabeçalho Basic-auth e no corpo da requisição, já que segredos de banco de dados costumam viajar dentro de SQL e JSON. Em qualquer outro lugar, a string não serve para nada.

Na AWS, o segredo nunca chega à VM. O ~/.aws/config do agente aponta para um auxiliar que entrega o ID real da chave de acesso com um segredo falso de 40 caracteres. A AWS CLI, o boto3 e o terraform assinam com o falso, e o Mac remove essa assinatura e reassina com o segredo real antes de a requisição sair. A chave secreta real nunca existe na VM, então nenhum comando que o agente escreva pode contê-la.

O SSH cobre os 43 logins de servidores da contagem da Apiiro. O SSH_AUTH_SOCK de uma máquina Bromure é uma ponte para o Mac: a VM pode pedir assinaturas, e os bytes da chave privada nunca atravessam. Chaves protegidas por YubiKey, Secretive ou 1Password podem ser oferecidas uma a uma, com o pedido de toque no seu Mac. O agente nunca precisa de sshpass -p, então não há senha para uma regra de permissão salvar.

VM da máquinaComando aprovadoGH_TOKEN=ghp_Xk3…a7Q gh api …"Yes, and don't ask again"settings.local.jsonguarda o substituto, que sófunciona via o proxy deste Macgit pusho commit inclui o arquivoSeu MacO proxy troca no fiosubstituto → token real, só emrequisições a github.comValores reais, no Keychaintokens, segredos AWS, chaves SSH:nunca na VM, de forma algumaPrompt before writeo push espera sua respostaA internetapi.github.comrecebe o token real, ocomando roda como sempreAlguém copia o arquivode um repo, fork ou commitantigo e testa o token:401 Bad credentials
O mesmo clique numa máquina Bromure. A regra de permissão continua guardando o comando, mas o token nele é um substituto. O push que o publicaria espera por um diálogo no Mac, e um substituto copiado do GitHub falha em qualquer lugar, porque só o proxy do seu Mac consegue transformá-lo no valor real.

Um substituto no GitHub não abre nada

Digamos que o arquivo chegue mesmo a um repositório público, a um fork e a uma dúzia de commits antigos. Quem o raspar recebe ghp_ e 36 caracteres que o GitHub nunca emitiu. Enviado do notebook dessa pessoa, falha na autenticação. Só o proxy do seu Mac sabe a qual token real ele corresponde, e esse proxy só atende a VM da sua máquina.

Se um substituto algum dia sair de dentro da máquina para o host errado, o proxy trata isso como uma tentativa de exfiltração. Ele bloqueia a requisição com HTTP 451, pausa a VM e abre um alerta intitulado This environment may have been compromised que mostra a credencial, o host ao qual ela pertence e o host para onde foi enviada. Assim, cada substituto também é uma armadilha, além de um marcador.

A rotação acontece num só lugar

A primeira instrução da Apiiro é rotacionar toda senha, token e chave que o arquivo já guardou, antes de limpar qualquer coisa. Com segredos reais espalhados por arquivos .env, histórico do shell e configuração do agente, isso é uma caçada. No Bromure, os valores reais ficam num só lugar: o painel Credentials da máquina, no Mac. Cole o novo token lá e o substituto na VM continua o mesmo, então as regras de permissão, scripts e configurações dentro da máquina continuam funcionando.

Uma Other API key também pode ser uma referência do 1Password como op://Engineering/npm/token. O Bromure a resolve no Mac e a resolve de novo a cada 2 minutos, então uma chave rotacionada no 1Password passa a valer em poucos minutos, sem reiniciar e sem nada para editar na VM.

Levar os segredos para esse painel é um passo só. Import env file… lê um .env ou ~/.bashrc existente, mostra as variáveis com os valores mascarados e as transforma em credenciais. A pasta do projeto deixa de precisar do arquivo .env de onde o agente lia as senhas.

O push espera por você

Em 539 dos repositórios que a Apiiro rastreou, um commit que creditava o Claude como coautor alterou o arquivo. Agentes fazem commit e push como parte de terminar uma tarefa.

Nas máquinas Bromure novas, todo serviço de forge começa em Prompt before write no painel Guardrails. Leituras passam. Um git push por HTTPS para num diálogo no Mac que mostra a operação, e nada sai até você responder. A política roda no proxy, então nada dentro da VM consegue desligá-la.

Um revisor que lê a linha

Os 215 segredos que nenhum scanner pegou eram senhas dentro de comandos. Um modelo que lê PGPASSWORD=… psql -h db.example.com num arquivo JSON commitado sabe o que está vendo.

Uma code review do Bromure acompanha um repositório do GitHub com um agente em uma das suas máquinas, em Every new commit, Every new pull request ou numa agenda semanal, e o Bromure elimina achados duplicados entre varreduras. Para o passo da Apiiro “procure na sua organização, histórico incluído”, Run Baseline Now inicia uma revisão avulsa do repositório inteiro. O GitHub é consultado a partir do Mac, e o agente de revisão trabalha com os mesmos substitutos que qualquer outro.

A transcrição também guarda o substituto

A contagem da Apiiro incluía logs de chat de agentes salvos. Os arquivos de sessão do próprio Claude Code na VM registram o que o agente viu, e numa máquina Bromure isso é o substituto. Os traces do Bromure no Mac guardam as trocas de token como prévias curtas do tipo sk-a…f9q3, e os bytes do segredo real nunca entram num registro de trace. Quando um segredo com cara de real aparece mesmo assim no tráfego, porque alguém o colou à mão na VM, o trace o marca como possível vazamento, e bromure-cli trace leaks lista todos. Ative Only allow credentials Bromure injects em Guardrails, e uma requisição com uma credencial que o Bromure não colocou ali é bloqueada e dispara o alerta de comprometimento.

Substitutos em todo comando

Tokens, segredos de banco de dados e chaves da AWS são substitutos na VM. Uma regra de permissão salva um valor que só funciona através do seu Mac.

Sem senha para SSH

A VM pede assinaturas ao Mac. A chave privada nunca atravessa, e sshpass -p nunca é necessário.

Pushes esperam no Mac

Prompt before write é o padrão em máquinas novas. Um push espera sua resposta, e quem impõe isso é o proxy.

Rotacione uma vez

Os valores reais ficam num só painel, ou no 1Password. Troque-os lá e todo substituto continua funcionando.

O que fazer esta semana

Da lista da Apiiro, para toda equipe que usa agentes de código:

  • Procure .claude/settings.local.json e .claude/settings.json na sua organização do GitHub, histórico incluído.
  • Rotacione todo segredo que esses arquivos já guardaram e depois faça a limpeza.
  • Rode git rm --cached .claude/settings.local.json e adicione o arquivo ao .gitignore do projeto e aos templates da sua organização.
  • Trate .claude/, .cursor/, .gemini/ e .specstory/ como arquivos .env na varredura de pre-commit e de CI, e garanta que o scanner pegue senhas dentro de comandos.

Depois, tire os segredos reais do alcance do agente. Numa máquina Bromure, o próximo “don't ask again” salva um substituto, quer o desenvolvedor tenha se lembrado das regras da Apiiro naquele dia, quer não.

Para o padrão mais amplo de agentes que copiam segredos para o histórico e os logs, veja A transcrição guardou o token. Para entender a troca em detalhe, leia o capítulo Credentials do manual.