O token do agente era o do proprietário
Em 23 de outubro, a LiteLLM publicou a causa raiz da sua versão 1.107.0 envenenada. A primeira credencial roubada não veio de um log. Veio da própria plataforma de agentes da LiteLLM, cuja API devolvia o token do GitHub que seus agentes na nuvem usavam para abrir pull requests: o token de um proprietário da organização, sem escopo restrito. A chave dessa API estava havia quatro meses e meio num arquivo de configuração público. No Bromure Agentic Coding, um agente que abre pull requests tem um substituto, o token real fica criptografado no Mac e nenhuma API o devolve.
Os agentes da LiteLLM precisavam de um token do GitHub para abrir pull requests. Receberam um que pertencia a um proprietário da organização. Uma única chamada de API com uma chave vazada o devolveu.
Em 23 de outubro, a LiteLLM publicou a cadeia de ataque por trás da sua versão 1.107.0, a que saiu em 21 de outubro com um ladrão de credenciais dentro. O texto se apresenta como «a fonte canônica da verdade» e corrige a primeira teoria da LiteLLM. Os tokens do GitHub roubados não vieram do tráfego de modelos registrado em log. O primeiro veio de um serviço experimental que a LiteLLM mantinha para seus próprios agentes de programação.
Cobrimos a versão em si em A versão tinha duas horas, e ali seguimos a primeira teoria. O relatório final começa em outro lugar. A primeira credencial que o atacante levou pertencia aos agentes.
Uma plataforma de agentes com o token de um proprietário
A LiteLLM Agent Platform era, nas palavras da LiteLLM, «um projeto experimental de agentes em nuvem auto-hospedados para abrir PRs em repositórios do GitHub». Para abrir pull requests, a plataforma tinha um token do GitHub. Esse token «não estava associado a uma conta de serviço e não tinha escopo restrito». Era o token OAuth da CLI do GitHub de um dos dois proprietários da organização.
A linha do tempo do relatório, em UTC:
- 27 de maio, 18:53. Um arquivo
opencode.jsoné commitado no repositório público LiteLLM-Labs/lite-harness. É a configuração de um harness de agentes e contém a chave mestra de uma implantação da Agent Platform. A equipe percebe, sobrescreve o commit com um force-push e marca as chaves expostas para revogação. «A revogação não aconteceu para a chave mestra.» - 10 de outubro, 21:27. A partir de 69.48.229.143, o atacante chama a
API da plataforma com essa chave.
GET /api/v1/managed_agents/agents?limit=100devolve a configuração de cada agente gerenciado e, com ela, o token do proprietário. - 10 de outubro, 22:11. Com o token do proprietário, o atacante envia
um branch descartável,
ci-tmp-a351cc, para BerriAI/litellm. O workflow dele envia seis segredos do repositório para webhook.site. Um deles,GH_TOKEN, é o token da CLI do GitHub de um segundo mantenedor. Vinte e quatro segundos depois, um segundo branch em BerriAI/litellm-auto envia o token de um terceiro mantenedor para o mesmo endpoint. As duas execuções são apagadas em menos de três minutos. - 12 de outubro. Mais workflows em repositórios privados enviam todos os segredos de repositório que conseguem alcançar.
- 21 de outubro, 10:08. Um push para o repositório de release com o token do segundo mantenedor é rejeitado pela proteção de branch. Cinco segundos depois, o token do proprietário desativa a proteção e força o push da mudança, que aponta o workflow de release para o imitador Berri-AI/litellm.
- 21 de outubro, 11:11. O token do segundo mantenedor aprova o portão de produção. Uma regra que exigia a aprovação de uma conta vinculada a um dispositivo no escritório da LiteLLM tinha sido afrouxada naquela semana, de modo que dois engenheiros quaisquer podiam publicar.
- 21 de outubro, das 11:39 às 11:46. Sete wheels chegam ao PyPI, depois as imagens no Docker Hub e no GHCR.
- 21 de outubro, 11:53. O token do terceiro mantenedor apaga os registros da execução de release. Às 12:24, o token do proprietário deixa a main limpa com um force-push.
O resumo da própria LiteLLM: «Este incidente forneceu uma ilustração contundente dos riscos de não rotacionar uma chave potencialmente exposta.» A empresa agora está revogando todos os tokens ligados a cada sistema interno e a cada plataforma de fornecedor.
Toda frota de agentes tem um token assim
A LiteLLM tem uma equipe de segurança competente, e sua configuração era
comum. Um agente que abre pull requests precisa de um token que possa
fazer push. O token mais à mão é o seu próprio login do gh, e foi esse
que os agentes da LiteLLM receberam.
Depois, o token precisa morar em algum lugar onde os agentes possam lê-lo: uma variável de ambiente, um arquivo de configuração, uma linha de banco de dados atrás de uma API que configura os agentes. Quem consegue ler esse lugar tem o token. Aqui bastou uma chave de API que vazou uma vez e nunca foi rotacionada.
Tokens da CLI do GitHub pioram a situação. A LiteLLM observa que eles «são emitidos após o 2FA e não estão sujeitos aos fluxos de reautenticação 2FA de “superusuário” do GitHub associados a ações sensíveis na organização ou no repositório». Com um deles, o atacante podia desligar a proteção de branch, forçar um push, aprovar um release e apagar as evidências.
Procure as mesmas peças na sua própria configuração: um GH_TOKEN no
ambiente do agente, uma configuração MCP com um token bearer dentro, uma
configuração de harness que alguém um dia vai commitar. O opencode.json
da LiteLLM continha mais do que a chave da plataforma. Também trazia tokens
bearer para seus servidores MCP do GitHub, do Linear e do Slack e a chave
do seu gateway de modelos, tudo em texto puro.
A mesma frota numa máquina Bromure
O Bromure Agentic Coding roda Claude Code, Codex, Grok, Kimi e omp em VMs Linux no seu Mac. Suas automações fazem o que a plataforma de agentes da LiteLLM fazia: um agente dispara num horário ou a partir de uma nova issue do GitHub, trabalha num worktree novo e envia um branch ou abre um pull request. Rode a cadeia da LiteLLM contra uma máquina que executa essas automações.
O agente tem um substituto
Adicione o token do GitHub à máquina em Credentials. Na inicialização,
o Bromure grava um falso na VM: um valor ghp_ com o comprimento certo, em
GH_TOKEN, na configuração do gh e em ~/.git-credentials. Os
gh pr create e git push do agente funcionam normalmente. Quando uma
requisição sai da VM para github.com, o proxy no Mac troca o falso pelo
token real, e só para esse host.
O token real fica criptografado no Mac, sob uma chave guardada nas Chaves do macOS. Ele nunca é gravado na VM, em nenhum arquivo nem variável. O manual diz sobre o canal entre o host e a VM: «Não existe requisição pela qual a VM possa pedir um token real de volta.»
Então o passo que abriu a organização da LiteLLM não tem nada a devolver.
Despeje o ambiente do agente, leia a configuração dele, liste todas as
automações pela API: o valor do GitHub em todas elas é o substituto. Cole-o
no seu próprio gh e o GitHub o rejeita, porque só o proxy daquela máquina
sabe o que ele representa.
A configuração commitada também tem substitutos
O vazamento começou com uma configuração de harness num repositório
público. Numa máquina Bromure, os arquivos de configuração que um agente lê
são gravados com substitutos. O token bearer de um servidor MCP, definido
no painel MCP da máquina, chega ao agente como um valor brm-mcp_…,
trocado em trânsito nas requisições para o host desse servidor. Uma chave
de modelo definida em Preferences › Models nunca entra na VM; o agente
vê um falso no formato do provedor. Qualquer outra chave de API vai em
Other API keys com um filtro de host, e a VM recebe um valor brm_… na
variável que você nomear.
Quando um agente commita a configuração por engano, ou um desenvolvedor a copia para um repositório de demonstração, o arquivo leva substitutos. Nada nele funciona fora daquele Mac, então nem um force-push nem uma lista de revogação que ninguém termina decidem se você é invadido.
Não há API de plataforma para chamar
O atacante da LiteLLM chegou à API da plataforma pela internet. As
automações do Bromure rodam no seu Mac. Nas palavras do manual: «não há
nenhum serviço em nuvem nem nenhuma superfície de rede de entrada». Os
gatilhos do GitHub e do Linear são consultados a partir do Mac a cada três
minutos com o token armazenado da máquina, que fica no host. Não há
endpoint de webhook, nem chave mestra, nem rota /managed_agents para uma
chave vazada abrir.
Um substituto roubado dispara um alarme
Os workflows de 10 de outubro enviavam segredos para webhook.site. Imagine
o mesmo movimento contra uma máquina Bromure: uma dependência envenenada ou
uma instrução injetada num prompt lê GH_TOKEN e o envia para
webhook.site.
O proxy examina cada requisição de saída, cabeçalhos e corpo, em busca de
substitutos indo para um host diferente daquele para o qual foram
emitidos. Um substituto ghp_ a caminho de webhook.site é bloqueado com
HTTP 451 antes que um único byte saia. A VM pausa, e um alerta no Mac
mostra This environment may have been compromised, com a prévia do
token, o nome da credencial, o host ao qual ela pertence e o host para o
qual foi enviada. Você pode desligar a máquina ou guardar o disco e a pasta
pessoal dela para investigação.
Com Unmatched traffic em Deny na política de sandbox da máquina, webhook.site nem chega a ser alcançável, e a tentativa aparece como uma linha vermelha na Security Timeline.
Um push espera por você
Máquinas Bromure novas começam com a política de escrita do GitHub em
Prompt before write. Leituras passam. Um git push ou qualquer outra
escrita em github.com para num diálogo no seu Mac:
Allow write on "GitHub" from workspace "release-bot"?
POST /repos/<owner>/<repo>/git/refs
Os movimentos do atacante eram todos escritas: os branches ci-tmp-, a
mudança no workflow de release, o force-push na main, a exclusão dos
registros de execução. A partir de uma máquina Bromure, cada um teria
parado nesse diálogo. Allow once não concede nada além daquela única
escrita, então o próximo push pergunta de novo. Um force-push continua
sendo um push em trânsito, e Prompt before write o controla.
Uma automação sem supervisão não pode esperar um clique, então dê a ela o
token mais restrito que faça o trabalho, sob uma política própria daquela
máquina. O token que você adiciona a uma máquina release-bot pode ser um
token de granularidade fina para um único repositório. Seu próprio login do
gh, com direitos de proprietário sobre tudo, não tem motivo para estar
naquela máquina.
O registro está no seu Mac
A LiteLLM cruzou hashes de tokens no log de auditoria do GitHub para saber qual token roubado fez o quê. Numa máquina Bromure, cada troca de uma credencial em trânsito aparece como uma linha Credential brokering na Security Timeline, com o token mascarado e o host. Cada escrita que você recusa é uma linha vermelha Guardrails com o método e o caminho. Num Mac registrado no bromure.io, os mesmos eventos chegam à sua organização, então um token usado a partir de uma máquina inesperada aparece sem que ninguém precise vasculhar logs de auditoria.
Agentes recebem substitutos
GH_TOKEN, a configuração do gh e ~/.git-credentials guardam um
ghp_ falso. O token real é trocado no Mac, só para os hosts do
GitHub.
Nenhuma API entrega tokens
As automações rodam no seu Mac sem superfície de entrada. Nada que a VM possa chamar devolve uma credencial real.
Vazamentos pausam a máquina
Um substituto enviado para qualquer lugar que não seja o próprio host é bloqueado com um 451, e a VM pausa enquanto você decide.
Escritas perguntam antes
Máquinas novas perguntam antes de cada push e de cada escrita no GitHub, force-pushes incluídos, com a requisição exata na tela.
Não dê o token ao agente
A LiteLLM está revogando todos os tokens e vai publicar suas mudanças de processo na semana que vem. Rotação, escopos restritos e contas de serviço ajudam. Cada uma dessas medidas ainda deixa um token funcional em algum lugar onde um agente pode lê-lo, e foi isso que o atacante foi procurar em 10 de outubro.
Numa máquina Bromure, o agente faz o trabalho com um token que só funciona através do próprio proxy, rumo ao único host para o qual foi emitido. Tudo o que o agente pode ler, um atacante que alcança o agente também pode, e numa máquina Bromure isso nunca inclui o token real.
Para ver como os mesmos substitutos resistem a um stealer que mira os arquivos de agentes de programação, leia O stealer levou os substitutos. Para as cópias que um agente deixa nas próprias transcrições, leia A transcrição guardou o token.