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

Você conferiu os quarenta minutos errados

Em 14 de agosto de 2026, a SOCRadar publicou uma reatribuição do comprometimento da cadeia de suprimentos do LiteLLM: 95% das organizações afetadas já haviam sido colhidas dias antes, pelo próprio scanner Trivy da Aqua Security. Cinco meses de resposta a incidentes foram dimensionados para a janela errada, porque todo controle da cadeia precisava que alguém nomeasse o pacote malicioso primeiro. As camadas de cadeia de suprimentos e de credenciais do Bromure Agentic Coding não precisam do nome.

Mandaram você conferir se havia instalado um pacote Python durante uma janela de quarenta minutos no dia 24 de março. Cinco meses depois, a SOCRadar descobriu que 95% das vítimas já haviam sido esvaziadas cinco dias antes, pelo próprio scanner de vulnerabilidades delas.

Em 14 de agosto, Ionut Arghire, da SecurityWeek, publicou a reatribuição feita pela SOCRadar de um dos maiores comprometimentos de infraestrutura de desenvolvimento do ano. A SOCRadar tinha o conjunto de dados em nível de registro, com os registros de coleta por organização de 2.188 entidades, e fez uma pergunta que ninguém havia feito: quando os dados de cada organização saíram?

Para 2.085 delas, a coleta havia terminado antes de os pacotes maliciosos sobre os quais todos foram avisados serem publicados. Isso é 95%. A conclusão da SOCRadar cabe em uma frase:

“Esse intervalo se encaixa no comprometimento a montante do Trivy, e não na janela de instalação do LiteLLM. Os 40 minutos que todos reportaram foram o ato final, não a peça inteira.”

Pesquisadores desmontaram o payload ainda em março. A SOCRadar mudou a linha do tempo. Por cinco meses a indústria conduziu a resposta a incidentes contra a janela errada, usando controles que nada podiam fazer até que alguém nomeasse a coisa ruim.

O ato final

Os famosos quarenta minutos pertencem ao LiteLLM. Em 24 de março, as versões 1.82.7 e 1.82.8 subiram no PyPI às 10:39 UTC e duraram cerca de quarenta minutos até o PyPI colocá-las em quarentena. As orientações da época mandavam tratar como suspeita qualquer instalação feita até as 16:00 UTC daquele dia.

O payload merece um momento pelo que ele derrota. É um arquivo .pth, litellm_init.pth, largado em site-packages. O Python executa arquivos .pth na inicialização do interpretador, importe alguém ou não o pacote que os instalou. Não há setup.py para inspecionar nem script de instalação para remover, então --ignore-scripts e todo controle construído sobre o modelo de execução de scripts de instalação passam ao largo. O The Hacker News cobriu o mecanismo em 12 de agosto e registrou o identificador em nível de ecossistema, CVE-2026-33634. A colheita ia para models.litellm[.]cloud.

Os atacantes publicaram esses wheels com um token de publicação do PyPI roubado, passando por cima do próprio pipeline de release do LiteLLM. Então, de onde veio esse token?

A peça inteira

Trivy. O scanner de vulnerabilidades de código aberto da Aqua Security, que dezenas de milhares de pipelines executam para encontrar exatamente esse tipo de problema.

O aviso da Aqua, GHSA-69fq-xp46-6x23, não suaviza a causa raiz. Um agente de ameaça rastreado como TeamPCP (UNC6780 para o Google) extraiu um token de acesso pessoal do ambiente do GitHub Actions do Trivy através de um workflow pull_request_target, no fim de fevereiro. A Aqua divulgou isso em 1º de março e rotacionou as credenciais. O aviso então diz que a rotação não foi “atômica (nem todas as credenciais foram revogadas simultaneamente)”. O atacante se instalou dentro da rotação e coletou os novos segredos à medida que a Aqua os emitia.

Dezenove dias depois, ele os gastou. Em 19 de março, deu force-push em 76 das 77 tags de versão do aquasecurity/trivy-action, e em todas as sete tags do aquasecurity/setup-trivy, apontando para commits maliciosos, de modo que um workflow fixado em @v0.28.0 buscava código novo sem que nada no repositório parecesse mudar. Usando a conta de serviço comprometida aqua-bot, publicou o Trivy v0.69.4 no GHCR, no ECR Public, no Docker Hub, em deb e em rpm. As imagens v0.69.5 e v0.69.6 do Docker Hub vieram em 22–23 de março, sobre uma credencial comprometida separadamente.

A análise da StepSecurity descreve o que o ladrão injetado fazia assim que um pipeline o executava:

  • Ler o ambiente de todo processo do runner através de /proc/*/environ.
  • Despejar a memória do processo worker do runner do GitHub Actions via /proc/<pid>/mem, usando Python codificado em base64. Isso recupera os segredos que um runner mascara em seus logs, já que o mascaramento é um filtro de log e a memória do processo guarda os valores sem máscara.
  • Varrer o sistema de arquivos atrás de chaves privadas SSH, credenciais git, tokens AWS, GCP e Azure, segredos do Kubernetes, configurações do Docker, credenciais de banco de dados, estado do Terraform e carteiras de criptomoedas.
  • Selar tudo isso sob uma chave pública RSA-4096 embutida no código, com criptografia híbrida, e enviar para scan.aquasecurtiy.org.

Leia esse nome de host de novo. É o próprio domínio do fornecedor com duas letras trocadas de lugar. Para um feed de reputação de domínios, ou para um analista passando os olhos no tráfego de saída às 2 da manhã, um scanner conversando com algo parecido com aquasecurity.org é a linha menos notável da página.

Se o envio falhasse, o ladrão criava um repositório público chamado tpcp-docs na conta do GitHub da própria vítima e anexava o butim como assets de release.

A SOCRadar cronometrou a primeira coleta dezoito minutos depois de o build malicioso subir.

Março de 2026 — janelas de exposição, em escala28 fev.19 mar.22–23 mar.24 mar.14 ago.PAT roubado · rotação não atômicatrivy-action — 76 de 77 tags reescritas~12 hsetup-trivy~4 hv0.69.4~3 hDocker Hub .5 / .6~10 hLiteLLM 1.82.7 / 1.82.840 min — a janela que todos checaram2.085 de 2.188 organizações — 95% — colhidas por inteiro dentro deste intervaloreatribuídoJanelas e versões conforme o aviso da Aqua Security GHSA-69fq-xp46-6x23. Contagens em nível de registro da SOCRadar,reportadas pela SecurityWeek em 2026-08-14. O eixo horizontal é esquemático dentro de cada dia.
Toda janela de artefato da campanha de março foi medida em horas, e cada uma se fechou antes do aviso que a nomeou. A janela sobre a qual o mundo agiu, quarenta minutos no PyPI cinco dias depois, foi a última a se abrir.

O que saiu dos pipelines

A Hudson Rock obteve o arquivo e a Help Net Security reportou seu tamanho em 13 de agosto: 153 GB, 433.909 arquivos, 118.829 despejos de runners de CI mapeados para 2.488 domínios corporativos. A contagem independente da CloudSEK, de cerca de 434.000 arquivos, colocou a exposição perto de 2.500 organizações e mais de 430.000 pipelines. Alon Gal, CTO da Hudson Rock, chamou o caso de “esforço global de divulgação ética” e disse que a magnitude “nos empurra para um mundo completamente novo quanto ao tipo de resposta necessária”. A leitura de Kevin Beaumont: “É uma violação massiva de cadeia de suprimentos por causa de segurança de IA malfeita.”

O detalhamento por organização da SOCRadar vai além. Mais de mil organizações perderam JWTs e tokens de autenticação. Centenas perderam chaves privadas, chaves de acesso AWS, tokens do GitLab, chaves de API da OpenAI, webhooks do Slack, tokens do GitHub Actions e chaves de API do Google. Uma organização perdeu cerca de 3.477 segredos individuais. Mil e cem expuseram os endereços de e-mail de seus committers. Os runners eram GitHub Actions, GitLab CI, Jenkins, Bitbucket, CircleCI e Buildkite. Alemanha, Brasil e França levaram a pior, e as coletas agora estão sendo negociadas no Telegram.

Todo controle daquela cadeia precisava de um nome

Enfileire as defesas que existiam em março e note do que cada uma precisava antes de poder agir.

O aviso precisava que a Aqua identificasse o comprometimento. A remoção precisava que o PyPI identificasse o pacote. A lista de IoCs precisava que alguém lesse scan.aquasecurtiy.org como hostil, e não como um erro de digitação de um fornecedor em que todo mundo confia. O checklist de “você instalou entre 10:39 e 16:00 UTC?” precisava das cinco horas certas. A reputação de domínio precisava que o domínio tivesse reputação. A reatribuição que contou a 2.085 organizações que elas estavam olhando para a semana errada precisava que um arquivo vazado viesse à tona e que uma equipe de pesquisa se sentasse com ele, cinco meses depois.

Cada um desses é um controle de identidade: age sobre uma coisa depois que alguém nomeia a coisa. Nomear é como o ecossistema compartilha conhecimento, e funciona, então isso não é uma queixa contra quem escreve avisos. É um ponto sobre ordem de operações. Um controle de identidade chega depois da nomeação, e nesta campanha cada artefato ficou no ar de três a doze horas. Os dezoito minutos entre a publicação e a primeira coleta respondem à pergunta de se essa ordem era boa o bastante.

Os controles que mudam o desfecho aqui são os que não precisam do nome.

Um relógio, não um oráculo

O Bromure Agentic Coding passa cada busca de pacote pelo proxy MITM do lado do host antes que um byte alcance a VM, e o portão de idade é a única camada de cadeia de suprimentos ligada por padrão. Ele recusa qualquer versão de pacote mais nova que um corte. Dois dias, de fábrica.

Ele não faz nenhuma consulta. Não tem opinião alguma sobre o mantenedor, o publicador, a assinatura ou o nome. Ele lê o timestamp de publicação e aplica um piso, partindo da ideia de que uma release fresca é a que tem mais chance de ter sido sequestrada uma hora atrás, e de que esperar essa janela passar custa quase nada a um desenvolvedor em atividade.

Contra esta campanha, isso é a briga inteira. LiteLLM 1.82.7 e 1.82.8 existiram por quarenta minutos. Um piso de dois dias significa que elas nunca existiram, de dentro da VM. O proxy retira as versões novas demais dos metadados do registro e reaponta latest e as demais dist-tags para a versão sobrevivente mais recente, então pip install litellm e todos os intervalos semver resolvem para 1.82.6 sem erro e sem nada que um agente possa contornar. Peça uma versão envenenada por pin exato e o backstop de busca de artefatos devolve um 451 cujo corpo declara a idade real do pacote diante do mínimo exigido. O pip imprime isso literalmente:

Bromure Supply-Chain Security blocked this request: pypi package
[email protected] published 34 minutes ago — policy requires 2 days minimum

O portão cobre npm, PyPI, Cargo, RubyGems e Packagist com dados completos de horário de publicação. Para o pip, ele faz uma consulta sob demanda à API JSON do PyPI, já que o índice PEP 503 padrão não carrega timestamps. Cada decisão cai no Log de Segurança (Janela → Log da Cadeia de Suprimentos…) na hora em que acontece.

CONTROLE DE IDENTIDADE — age depois que o nome existeartefato publicadot + 0alguém percebehoras … mesesatribuiçãoaviso, IoCso controle bloqueiasegredos saem em t + 18 minreatribuído em t + 5 mesesPISO TEMPORAL — age na hora da buscaartefato publicadot + 0o agente resolve a dependênciao proxy lê o timestampidade < 2 dias → não servidometadados reescritos, ou 451nenhum nome exigido,nenhum aviso exigido
Um controle de identidade precisa esperar o mundo nomear o artefato. Um piso temporal nunca aprende o nome e nunca precisa: ele compara um timestamp de publicação com um número que você escolheu de antemão.

O que um despejo de memória consegue

A melhor jogada do ladrão foi ler a memória do worker do runner de CI para recuperar segredos que o mascaramento de logs havia escondido. Funciona porque o mascaramento é cosmético e o processo guarda os valores.

Rode isso contra um workspace Bromure e ele recupera marcadores, porque o processo guarda marcadores. A fronteira do fio é o mecanismo central do produto: cada credencial que você configura aparece dentro da VM como um falso que preserva a estrutura, enquanto o valor real fica criptografado no seu Mac. O proxy do host o substitui no fio depois que a requisição já saiu da VM, e apenas quando a requisição vai para o host para o qual aquela credencial foi cunhada.

Percorra a própria lista do ladrão contra uma VM Bromure:

/proc/*/environ

ANTHROPIC_API_KEY é sk-ant-api03-brm-…. OPENAI_API_KEY é sk-brm-…. GH_TOKEN é ghp_ mais 36 caracteres. Preservam a estrutura, então claude e gh os aceitam sem reclamar, e não valem nada para mais ninguém.

/proc/<pid>/mem

Despejar o processo recupera os mesmos marcadores que o ambiente guarda. Nenhuma cópia privilegiada se esconde mais fundo na memória: o proxy do host faz a substituição fora da VM, depois que os bytes já saíram.

Chaves privadas SSH

Não há nenhuma. SSH_AUTH_SOCK aponta para uma ponte ssh-agent sobre a porta vsock 8444. Os bytes da chave privada nunca entram no convidado e não podem ser lidos de lá. O Bromure deixa o agente de login do seu macOS sem exposição, por projeto.

Nuvem, k8s, Docker, banco de dados

~/.aws/config, ~/.kube/config, ~/.docker/config.json, ~/.git-credentials, ~/.config/doctl/config.yaml. Todos presentes, todos preenchidos, todos falsos: brm-k8s-…, brm-docker-…, brm-db-…, glpat- mais 20, dop_v1_ mais hexadecimal.

O Bromure deriva cada falso a partir do valor real mais um salt de 32 bytes próprio da instalação, via HKDF-SHA256, então uma ferramenta que tira a impressão digital da própria chave (o Claude Code guarda em cache um hash de chave) nunca vê a credencial girar entre sessões. Também não há chave para esquecer de ligar. O proxy é a única rota da VM para a rede, então uma requisição que o contorna carrega um marcador e falha na autenticação lá em cima. A fronteira falha fechada, por construção.

O typosquat que ninguém jamais tinha visto

Agora a exfiltração. Em 19 de março, scan.aquasecurtiy.org não tinha reputação, nem histórico, nem motivo para estar na lista de bloqueio de ninguém. Foi por isso que o TeamPCP o escolheu.

O detector de comprometimento do Bromure não lê nada disso. Ele observa as suas credenciais, e não a reputação do destino. Um autômato de Aho-Corasick construído a partir dos próprios falsos cunhados para o workspace varre cada requisição de saída, cabeçalhos e corpo. Quando um desses falsos aparece numa requisição destinada a um host fora do escopo para o qual foi cunhado, o proxy trata a requisição como tentativa de exfiltração:

  1. O proxy recusa a requisição com HTTP 451 e não encaminha um único byte ao destino.
  2. O Bromure pausa a VM na hora.
  3. Um alerta nomeia o que aconteceu: uma tentativa de saída de vazar uma credencial de sessão para um host para o qual ela não foi cunhada.

O Bromure então marca o workspace como comprometido, e o próximo lançamento exige apagar a imagem de disco da VM e o home persistente antes de dar boot. Seus tokens, chaves SSH e configurações de workspace sobrevivem a isso. O Inspetor de Traces (⇧⌘I) e bromure-cli trace leaks mostram o host ofensor e a requisição exata, então você descobre em um minuto se a culpa foi de uma dependência ou de uma instrução injetada por prompt.

Um falso em formato AKIA com escopo em amazonaws.com, aparecendo num POST para scan.aquasecurtiy.org, dispara esse autômato na primeira tentativa. O detector nunca ouviu falar do domínio e não precisa ouvir. Ele sabe que essa credencial tem uma única família legítima de destinos, e que este é outro. Não há nada a habilitar; ele roda em cada requisição.

A linha do tempo que você já tem

O problema de registro aqui sobrevive ao incidente.

Pesquisadores reconstruíram a linha do tempo de fora, a partir de um arquivo vazado de 153 GB que precisou vir à tona, ser obtido e ser peneirado antes que alguém pudesse dizer qual janela importava. As vítimas não conseguiam responder a partir dos próprios registros, porque esses registros deixavam de fora os dois fatos que decidiam a questão: quais artefatos seus pipelines buscaram e quando, e para onde os processos resultantes mandaram tráfego.

Um workspace Bromure produz esse registro como efeito colateral de como funciona. Cada busca cruza o proxy do host, então o Log de Segurança guarda cada decisão de portão de idade, de OSV, de socket.dev e de 451 como um fluxo ao vivo. Cada requisição cruza o mesmo proxy, então bromure-cli trace ls te dá host, método, status, latência e as marcas swap×N / LEAK×N por workspace, trace hostnames lista cada host distinto que uma sessão contatou, e trace summary agrega tudo. Tudo isso fica criptografado em repouso no seu Mac, sob a chave mestra do cofre.

Assim, a pergunta que a SOCRadar respondeu em agosto, fui coletado, e quando?, vira uma consulta de dois minutos que você mesmo roda em março, contra seus próprios dados, sem esperar que alguém publique o nome certo.

É esse o argumento. Ninguém consegue tornar o ecossistema mais rápido em nomear coisas: março mostrou a resposta a incidentes do próprio fornecedor deixando para trás um rastro de dezenove dias e uma atribuição errada. O que você pode mudar é se o seu desfecho depende do nome. Ponha um relógio na frente do registro, ponha marcadores na máquina que roda o código, e mantenha seu próprio log do que ela buscou e com quem falou.


Fontes: SecurityWeek, “Trivy, Not LiteLLM Behind the 2,500 Org Compromise” (14 de agosto de 2026) · Aviso da Aqua Security GHSA-69fq-xp46-6x23 · StepSecurity, “Trivy Compromised a Second Time” · The Hacker News (12 de agosto de 2026) · Help Net Security (13 de agosto de 2026) · CrowdStrike, “From Scanner to Stealer”