A chave apagada ainda estava no histórico
Em 8 de outubro, uma campanha chamada GhostAction usou duas contas de mantenedores sequestradas para commitar um workflow chamado security-audit.yml em 346 repositórios, entre eles o athenadriver da Uber e o pyxel, com 18 mil estrelas. Em 9 de outubro, a Socket já contava mais de 500 contas e dezenas de milhares de repositórios. O workflow roda git log -p --all e faz grep em cada commit já feito atrás de chaves da Anthropic, OpenAI, OpenRouter, GitHub e AWS, inclusive as apagadas um commit depois. No Bromure Agentic Coding, o agente só tem substitutos, então as chaves que ele escreve no histórico de um repositório não valem nada para quem as encontra.
Apagar uma chave de um arquivo leva um commit. O commit anterior guarda a chave enquanto o repositório existir.
Às 13h40 UTC da quinta-feira, 8 de outubro, a conta do GitHub de Takashi
Kitao, autor da engine de jogos pyxel, começou a commitar um arquivo chamado
.github/workflows/security-audit.yml. Quatro minutos depois, ele estava em
27 dos seus repositórios. Às 21h10 UTC, a conta henrywoo, autor original do
athenadriver da Uber, começou a mesma varredura. Às 21h26, já tinha chegado
a 318 repositórios, incluindo 279 forks e o próprio uber/athenadriver, onde
a conta tem acesso de escrita.
A Socket publicou sua
análise em 9 de
outubro e atribuiu os commits ao GhostAction, uma campanha de roubo de
credenciais vista pela primeira vez em setembro de 2025. No fim daquele dia,
a atualização da Socket contava mais de 500 contas do GitHub que tinham
commitado o workflow em “dezenas de milhares de repositórios” desde 7 de
outubro. O The Hacker
News
e a StepSecurity cobriram a mesma onda. A Socket descobriu que o workflow já
tinha rodado em repositórios afetados, e que ainda estava nos branches padrão
de kitao/pyxel e uber/athenadriver quando o post foi ao ar.
O que o security-audit.yml faz
O workflow se chama “Security Audit”. Ele roda em workflow_dispatch e a cada
push em qualquer branch ou tag, sem filtro nenhum. O passo de checkout usa
fetch-depth: 0, que baixa todos os branches, todas as tags e o histórico
completo. Depois, um único passo faz dois trabalhos.
O primeiro vai atrás dos secrets de GitHub Actions do repositório. Antes de
commitar o workflow, o atacante leu os workflows existentes do repositório e
escreveu no payload os nomes dos secrets que eles usam. No pyxel, eram
CARGO_REGISTRY_TOKEN, PERSONAL_ACCESS_TOKEN, PYPI_PASSWORD e
PYPI_USERNAME.
O segundo é novo nesta onda. O passo faz grep na árvore de trabalho com 13
padrões de credenciais, depois roda git log -p --all e procura os mesmos
padrões em até 200.000 linhas de histórico:
- Serviços de IA: Anthropic (
sk-ant-), chaves de projeto da OpenAI (sk-proj-), OpenRouter (sk-or-) - Controle de código: tokens clássicos do GitHub (
ghp_), tokens de granularidade fina (github_pat_), tokens do GitLab (glpat-) - AWS: IDs de chave de acesso, IDs de chave temporária, chaves secretas e tokens de sessão. O passo também guarda duas linhas de contexto em volta de cada ID de chave, então a chave secreta commitada ao lado vai junto.
- SaaS e nuvem: chaves de API do Google, tokens do Slack, chaves do SendGrid
Tudo sai em um único POST de curl por HTTP sem criptografia para
193.32.204.199. Cada execução informa o nome do repositório, tenha
encontrado algo ou não.
A Socket aponta o público-alvo. Dois dos repositórios de henrywoo são
pyllama e chatllama, projetos de machine learning cujos colaboradores e
autores de forks “provavelmente têm esses tipos de chave”. Uma chave da
Anthropic ou da OpenAI pode ser revendida como inferência faturável, e cada
prompt enviado por ela passa pelas mãos do comprador.
O histórico guarda tudo
A orientação de correção da Socket tem uma linha que deveria preocupar
qualquer um que já limpou uma chave vazada editando um arquivo: “Rotacione
toda credencial encontrada em qualquer commit, esteja ela ainda na árvore
atual ou não.” Uma chave removida no commit seguinte continua no commit
anterior. git log -p --all lê os dois.
Agentes de código engordam esse histórico rápido. O State of Secrets Sprawl 2026 da GitGuardian constatou que commits públicos coassinados pelo Claude Code vazavam secrets em 3,2% dos casos, contra uma base de 1,5% no GitHub público como um todo. O mesmo relatório contou 1.275.105 secrets vazados ligados a serviços de IA em 2025, 81% a mais que no ano anterior.
O roteiro é conhecido de quem já viu um agente fazer passar um teste de
integração que falhava. O agente tem a chave no ambiente. Ele escreve a chave
em um arquivo de config, uma fixture de teste ou um .env que ainda não está
no ignore, porque isso faz o teste passar. Ele commita. Depois, o
desenvolvedor ou o agente move a chave para uma variável de ambiente e
commita de novo. A árvore parece limpa. O histórico tem a chave, todo fork
tem o histórico, e um workflow com fetch-depth: 0 o lê.
O token que escreveu o workflow
A Socket não determinou como as duas contas foram tomadas. O The Hacker News
aponta a origem mais provável: um personal access token vindo de logs de
infostealers ou de dumps de credenciais. Logs de infostealers vêm de máquinas
de desenvolvedores. Num notebook, o token do GitHub fica em
~/.config/gh/hosts.yml, em ~/.git-credentials e muitas vezes em
GH_TOKEN, legível por qualquer coisa que rode como o desenvolvedor,
inclusive o que um agente de código instala ou executa.
Um token basta. Com ele, o atacante pode listar os repositórios em que a
conta pode escrever, ler os workflows deles em busca de nomes de secrets e
commitar em cada um. A varredura de henrywoo cobriu 318 repositórios em 16
minutos.
O mesmo repositório, escrito a partir de uma máquina Bromure
No Bromure Agentic Coding, o agente roda dentro de uma VM Linux no seu Mac, e cada requisição HTTP que a VM faz passa por um proxy no host. Sua chave da Anthropic, seu token do GitHub e suas chaves da AWS ficam criptografados no Mac. Agora solte o GhostAction num repositório escrito por uma máquina Bromure, e no desenvolvedor que o escreveu.
O histórico guarda substitutos
Dentro da VM, cada credencial é um substituto com o formato da real:
ANTHROPIC_API_KEY é sk-ant-api03-brm-…, uma chave da OpenRouter é
sk-or-v1-brm-…, GH_TOKEN e ~/.git-credentials guardam ghp_ mais 36
caracteres. Cada um é derivado do valor real e de um salt por instalação com
HKDF-SHA256, e não pode ser convertido de volta nele. O proxy troca pelo
valor real na rede, e só numa requisição para o host ao qual a credencial
pertence.
Então, quando o agente escreve a chave dele em config/test.py para fazer o
teste passar, ele escreve o que tem. Quando commita, o commit guarda
sk-ant-api03-brm-…. Quando o commit de limpeza vem depois, o histórico
guarda esse mesmo substituto.
O grep do GhostAction o encontra. sk-ant- casa com sk-ant-api03-brm-, e
ghp_ casa com um token do GitHub substituto. O POST para 193.32.204.199
os leva, e o comprador os testa. A Anthropic rejeita a chave e o GitHub
rejeita o token, porque a única coisa que os transforma em credenciais
válidas é o proxy no seu Mac, para requisições que saem da sua VM. O conselho
da Socket de rotacionar toda chave encontrada em qualquer commit tem uma
resposta curta para este repositório: nenhuma dessas chaves jamais funcionou
fora do seu Mac, e os substitutos de provedores de IA trazem -brm- no nome,
então dá para identificá-los numa varredura do histórico.
O token no notebook também é um substituto
A outra metade da campanha é a conta em si. Rode seus agentes numa máquina
Bromure, e o token do GitHub que eles usam fica no Mac. Qualquer coisa que
rode na VM, seja o script de instalação de um pacote, um stealer ou uma
injeção de prompt, encontra um substituto ghp_ em ~/.git-credentials e
GH_TOKEN. Um atacante que o reutiliza da própria máquina recebe um 401 do
GitHub.
O stealer também precisa mandar o que pegou para algum lugar, e essa
requisição passa pelo proxy. Um substituto tem um único destino legítimo, o
host para o qual foi gerado. O proxy examina cada requisição de saída,
cabeçalhos e corpo, atrás de qualquer substituto indo para outro lugar. Um
token ghp_ a caminho do servidor de um stealer recebe HTTP 451, nada é
encaminhado, a VM pausa, e This environment may have been compromised
abre no Mac com uma prévia do token, o nome da credencial, o host ao qual ela
pertence e o host para onde foi enviada.
Um push de workflow espera um clique
Worms dessa família nem sempre esperam um comprador. O payload do
tensorlake na semana passada
commitava arquivos nos repositórios das vítimas direto da máquina do
desenvolvedor. Máquinas Bromure novas usam Prompt before write para toda
forja git. Um push é uma escrita, então commitar security-audit.yml num
repositório a partir da VM para num diálogo do macOS, Allow write on
"github.com" from workspace "…"?, que mostra
POST /owner/repo.git/git-receive-pack como ele apareceu na rede. A
varredura de henrywoo fez 318 dessas escritas em 16 minutos. Numa máquina
Bromure, a primeira espera por você, e você não pediu ao seu agente para
adicionar uma auditoria de segurança a 318 repositórios.
Todo uso do token fica registrado
A Security Timeline registra cada troca de credencial como uma linha
Credential brokering: qual token, qual host, qual máquina, a que horas.
Quando você precisar saber se o seu token do GitHub fez push de alguma coisa
em 8 de outubro entre 21h10 e 21h26, a resposta está no seu Mac, num registro
que o agente não consegue editar. Com o rastreamento ligado,
bromure-cli trace leaks lista cada requisição em que um token apareceu no
host errado.
Limpando depois de uma chave
O conselho padrão depois de uma chave vazada é rotacioná-la, reescrever o
histórico com git filter-repo, forçar o push e torcer para que nenhum fork
ou espelho tenha copiado os commits antigos antes. O GhostAction mostra por
que essa última parte falha. A Socket contou 279 forks num único namespace,
cada um com o histórico e, agora, com o workflow. Uma chave que chegou a um
commit público está perdida, mostre a árvore atual o que mostrar.
Uma máquina Bromure muda o que chega ao commit, para começo de conversa. O
agente pode ser tão descuidado com chaves quanto os números da GitGuardian
dizem que agentes são. Ele pode colar uma chave numa fixture, commitar um
.env e esquecer. Ele nunca teve uma chave válida, então nada desse
histórico pode prejudicar você.
O commit guarda um substituto
Os agentes na VM só veem substitutos com o formato das chaves reais. Uma
chave escrita num arquivo e commitada é uma string -brm- que só funciona
através do proxy no seu Mac.
A colheita recebe 401
Os padrões do GhostAction casam com os substitutos e os enviam. Anthropic, OpenAI e GitHub os rejeitam em qualquer lugar fora do seu Mac.
Um token roubado para a máquina
O token do GitHub na VM também é um substituto. Enviado para qualquer lugar que não seja o GitHub, ele recebe um 451, a VM pausa, e um alerta nomeia o token e o host.
Pushes esperam um clique
Prompt before write segura cada push da VM num diálogo do macOS que mostra a requisição, e a Security Timeline registra cada uso do token.
O próximo grep
Esta é a terceira onda do GhostAction em treze meses. A primeira, em setembro de 2025, levou 3.325 secrets de CI. A segunda, de 31 de agosto a 30 de setembro deste ano, foi atrás de 2.577 secrets em 772 repositórios. Esta acrescentou o histórico completo e três padrões de provedores de IA. A próxima onda vai acrescentar mais padrões, e o histórico que ela lê só cresce.
Rode seus agentes onde as chaves que eles tocam são substitutos, e o histórico que eles deixam para trás não guarda nada que um comprador possa usar. Para saber mais sobre como os substitutos funcionam e o que acontece quando um vaza, leia O agente vazou a própria chave e A chave nunca expirou.