Envenenaram o LiteLLM durante 40 minutos e apanharam segredos de 2500 organizações. E o mais me incomoda é que quase ninguém escolheu instalá-lo.

Olá a todos!

Disclaimer habitual antes de começar: eu corro LLM’s locais, defendo infraestrutura própria neste blog até à exaustão, e sim, tenho um preconceito assumido a favor do on-premise. Portanto, quando eu pego num incidente de segurança e o uso para dizer “estão a ver, é por isto que eu bato nesta tecla”, façam o desconto que quiserem. Só que desta vez há uma armadilha à espera de mim, e eu vou cair nela de propósito, à vossa frente, porque é a parte mais interessante da história. O LiteLLM, a ferramenta que foi envenenada nesta campanha, é precisamente o tipo de coisa que se corre local, na tua própria máquina, no teu próprio gateway. Não é uma nuvem que te trai. É software que instalas em casa. E foi comprometido na mesma. Se eu terminasse este post com “usem local e ficam a salvo”, estava a mentir-vos. Vamos ver porque é que a lição verdadeira é bastante mais fina do que isso.

O que aconteceu, em quatro frases

Em março, alguém carregou para o PyPI duas versões maliciosas do LiteLLM, a 1.82.7 e a 1.82.8, com código desenhado para roubar segredos das máquinas que as instalassem. Estiveram vivas cerca de 40 minutos no dia 24 de março, a partir das 10:39 UTC, até o PyPI as pôr em quarentena, embora o próprio projeto peça que se trate como suspeita qualquer instalação feita nesse dia até às 16:00 UTC. O que essas versões faziam era simples de descrever e feio de imaginar: liam variáveis de ambiente, chaves SSH, credenciais de cloud, tokens de Kubernetes e passwords de bases de dados, cifravam tudo, e enviavam para um domínio controlado pelos atacantes, models.litellm[.]cloud, que não tem nada a ver com o projeto legítimo apesar do nome escolhido a dedo para enganar. E depois, meses mais tarde, uma empresa de threat intelligence, a CloudSEK, apareceu com um dataset construído a partir de qualquer coisa como 434 mil ficheiros capturados aos atacantes, que aponta para exposição potencial de mais de 2500 organizações.

Put a pin nesse último número, porque eu vou voltar a ele e a honestidade obriga a dizer já uma coisa: 2500 não é uma contagem de vítimas. Já lá vamos.

O detalhe técnico que devia gelar qualquer administrador de sistemas

Se há um pormenor nesta história que eu quero que fique convosco depois de fecharem o separador, é este.

A versão 1.82.8 incluía um ficheiro chamado litellm_init.pth. Para quem não vive dentro do empacotamento de Python, um ficheiro .pth não é código que tu importas. É código que o interpretador de Python executa sozinho, no arranque, sempre que um processo Python começa naquele ambiente. Reparem no que isto significa na prática, porque é diabólico. Não era preciso o teu código fazer import litellm. Não era preciso sequer saberes que o LiteLLM lá estava. Bastava arrancar qualquer processo Python naquele ambiente, um script de deploy, um cron job, um teste, um notebook, e o payload corria. Silenciosamente. Antes de qualquer linha tua.

Isto inverte por completo a pergunta que fazemos por instinto quando sai um aviso destes. A pergunta habitual é “a minha equipa usa LiteLLM?”. Errado. A pergunta certa passou a ser “alguma coisa na minha máquina instalou o LiteLLM, mesmo que eu nunca o tenha pedido?”. E aqui está o veneno dentro do veneno: o LiteLLM é uma dependência transitiva de imensas ferramentas de orquestração e de frameworks de agentes. Ou seja, podias tê-lo puxado para dentro do teu ambiente sem nunca o teres escolhido, agarrado à cauda de outra coisa qualquer que instalaste de boa-fé. Se não fixaste as versões das tuas dependências, uma dependência de uma dependência tua pode ter trazido isto para casa sem te pedir licença.

E enquanto o payload andava por lá, o que é que ele procurava com mais apetite? Segundo a análise da Unit 42, ia direto às variáveis de ambiente que guardam chaves de API de modelos. Incluindo, e leiam isto devagar, OPENAI_API_KEY e ANTHROPIC_API_KEY. As chaves de acesso aos modelos de fronteira, expostas em texto no ambiente, prontas a serem levadas. Guardem esta imagem também, porque ela é o coração do argumento que eu vou construir na segunda metade do post.

O número dos 2500, e porque é que eu não gosto de o dar sem contexto

Volto ao alfinete. A CloudSEK diz que o material que obteve mapeia exposição potencial a mais de 2500 organizações, a partir de cerca de 434 mil ficheiros capturados. E publicou aquilo como uma ferramenta pública de consulta, onde podes procurar por nome ou domínio e filtrar por nível de confiança, High ou Medium. Nas entradas aparecem nomes de fazer subir o sobrolho: NVIDIA, Cisco, Deloitte, Volkswagen, FedEx, Siemens, X Corp.

Antes de alguém partilhar isto no LinkedIn com um “a Siemens foi hackeada”, parem. Eu quero ser rigoroso com o que este dataset prova e com o que não prova, porque é exatamente na diferença entre as duas coisas que se distingue análise de pânico.

O número de 434 mil conta ficheiros capturados e eventos de exfiltração, não pipelines distintos nem jobs únicos. A própria CloudSEK diz que, grosso modo, um ficheiro capturado corresponde a uma execução de job, mas recusa apresentar o total como jobs únicos sem deduplicação e verificação independentes. Ou seja, o número grande é uma contagem de despojos, não de organizações penetradas.

E os nomes? Uma correspondência de confiança alta afirma apenas de quem é que cada ficheiro veio, cruzando sinais de identidade do ambiente de CI capturado, sobretudo a identidade do host e os domínios legítimos de quem faz commit, e exige que o próprio domínio da organização apareça para ganhar o rótulo de topo. As correspondências que assentam só em namespaces de repositório ficam-se pela confiança média. E nenhuma delas, nem as de confiança alta, prova que alguma credencial roubada foi de facto usada contra aquela empresa. É precisamente por isso que tanto a CloudSEK como o LiteLLM dizem a mesma coisa aos afetados: rodem as credenciais, não esperem por prova. A ausência de prova de uso não é prova de segurança.

O que está confirmado, e é grave, é o impacto a jusante em casos concretos. A Checkmarx disse que credenciais obtidas através do ataque ao Trivy, que é a origem de toda esta cadeia e já lá chego, permitiram acesso não autorizado aos seus repositórios de GitHub e a publicação de artefactos maliciosos. A Mercor confirmou que foi afetada pelas versões maliciosas do LiteLLM e que conteve atividade não autorizada. E o CERT-EU avaliou com confiança alta que uma conta AWS da Comissão Europeia foi comprometida através do ataque à cadeia de fornecimento do Trivy, com cerca de 91,7 GB de dados comprimidos exfiltrados. Isto não é exposição potencial. Isto aconteceu.

Portanto, o retrato justo é este: não sabemos se 2500 organizações foram violadas, quase de certeza não foram todas, mas sabemos que os ficheiros existem, sabemos que alguns alvos de altíssimo perfil foram efetivamente comprometidos, e sabemos que quem tocou nas versões erradas tem de assumir o pior e agir. Entre uma intenção e um facto, quando o assunto é a tua segurança, escolhe sempre o facto. E o facto aqui é que os segredos saíram.

Isto não começou no LiteLLM. Começou num scanner de segurança.

Aqui está a ironia que devia doer a toda a indústria. A porta de entrada de tudo isto não foi um brinquedo de IA experimental. Foi uma ferramenta de segurança. O LiteLLM é apenas um elo de uma campanha maior de ataque à cadeia de fornecimento, que a comunidade batizou de TeamPCP e que a Google segue como UNC6780, e que está ligada ao Trivy, o scanner de vulnerabilidades da Aqua Security. Sim, leram bem. A cadeia arranca num scanner que serve, precisamente, para encontrar problemas de segurança.

Segundo a Aqua, os atacantes mantiveram acesso depois de uma rotação de credenciais incompleta e, no dia 19 de março, fizeram force-push de commits maliciosos para 76 das 77 tags de versão do trivy-action e para as sete tags do setup-trivy, ao mesmo tempo que publicavam uma release maliciosa do Trivy 0.69.4. A partir daí, com credenciais e tokens apanhados nesse envenenamento, a coisa espalhou-se. O comprometimento do ecossistema está registado como CVE-2026-33634 e foi adicionado ao catálogo de vulnerabilidades conhecidas exploradas da CISA a 26 de março. O registo do CVE já lista o BerriAI LiteLLM 1.82.7 até 1.82.8 como afetado, ao lado dos componentes do Trivy.

Como é que as releases maliciosas do LiteLLM chegaram concretamente ao PyPI é o ponto onde as várias análises publicadas discordam. A CloudSEK diz que foi o build envenenado a produzir e publicar as versões. O relatório de incidente do próprio LiteLLM aponta para um upload direto ao PyPI que contornou o workflow oficial de CI/CD. A Unit 42 descreve atacantes a visar tokens de publicação do PyPI depois da brecha do Trivy. Quando confrontada com a discrepância, a CloudSEK respondeu, e parafraseio, que não são explicações concorrentes mas fases diferentes da mesma cadeia de ataque: a evidência dela cobre como a credencial foi obtida, e as outras análises cobrem como foi depois usada. O aviso da PyPA conta essencialmente a mesma sequência: um token de API exposto através da dependência comprometida do Trivy, e a seguir usado para carregar as duas versões.

Reparem no padrão, porque é o mesmo que se repete em quase todos estes incidentes. Ninguém “hackeou o LiteLLM” no sentido hollywoodesco. Roubaram uma credencial num sítio, usaram-na noutro, e a confiança que a nossa infraestrutura deposita automaticamente em cada elo da cadeia fez o resto do trabalho de propagação. O elo mais fraco não foi o código do LiteLLM. Foi um token que andava por aí com validade longa e permissões a mais.

E agora a armadilha em que eu prometi cair

Chegámos ao momento em que, se este fosse um post preguiçoso, eu escrevia: “por tudo isto, corram os vossos modelos localmente e não dependam de nuvens”. E vocês fechavam o separador a pensar que tinham lido mais um panfleto do gajo do on-premise.

Só que o LiteLLM é local. É um gateway de IA open source que instalas na tua própria infraestrutura para ligar as tuas aplicações a vários fornecedores de modelos. Muita gente corre-o self-hosted, num container, na sua própria rede. Correr localmente não impediu nada disto. O envenenamento veio pela cadeia de fornecimento, pelo PyPI, agarrado a uma dependência. A tua máquina local instalou o veneno com o mesmo entusiasmo com que uma VM na cloud o faria. Portanto “local versus cloud” é a pergunta errada, e eu, que passo a vida a fazer o elogio do local, sou o primeiro a ter de dizê-lo em voz alta.

Então o argumento a favor da infraestrutura local morre aqui? Não. Muda de forma, e fica mais forte. Deixem-me reconstruí-lo do zero, com honestidade, porque é assim que ele resiste.

O que a infraestrutura local realmente te dá não é imunidade. Limita o raio de explosão.

Segurança séria não se mede por “fui atacado, sim ou não”. Isso vai acontecer a toda a gente, mais cedo ou mais tarde. Mede-se por “quando fui atacado, quanto é que o atacante conseguiu levar antes de bater numa parede?”. A palavra que interessa é blast radius, o raio de explosão. E é aqui que a infraestrutura própria, bem desenhada, muda tudo. Não porque não te deixa ser comprometido, mas porque estrangula o que o comprometimento consegue fazer.

Peguemos neste incidente concreto e passemos os factos por três perguntas. Não “o LiteLLM foi envenenado”, que foi. As três perguntas que importam.

Primeira: o que é que havia no ambiente para roubar? O payload foi à procura de OPENAI_API_KEY e ANTHROPIC_API_KEY, entre outros segredos. Ora, se a tua stack de IA corre contra modelos que são teus, servidos na tua própria infraestrutura, essas chaves de fronteira simplesmente não existem no teu ambiente. Não há OPENAI_API_KEY para levar quando não estás a pagar tokens a ninguém. O segredo mais apetecível deste ataque, o passe de acesso aos modelos de fronteira, só está lá para ser roubado porque o modelo vive noutro sítio e tu precisas de uma chave para lhe falar. Tira o modelo de fora da equação e tiras o segredo do ambiente. Isto não é filosofia, é redução da superfície de ataque a puro contabilismo: menos segredos no ambiente, menos coisas para exfiltrar.

Segunda: para onde é que os dados iam sair? O payload cifrava os segredos e mandava-os para models.litellm[.]cloud, um domínio externo, controlado pelos atacantes. Esta é a parte que devia fazer qualquer arquiteto de sistemas endireitar-se na cadeira. Um ambiente de IA on-premise sério não tem saída livre para a internet. Tem egress filtrado, com uma allowlist explícita de destinos. Naquele cenário, o payload podia correr à vontade, ler tudo, cifrar tudo, e depois embater numa firewall que não deixa um container de inferência abrir uma ligação para um domínio aleatório que ninguém aprovou. O roubo acontece; a exfiltração falha. E um segredo que foi lido mas nunca conseguiu sair da tua rede é um susto, não uma brecha.

Terceira: quanto tempo é que a credencial roubada valia? O FBI, num aviso de 2 de julho, a FLASH-20260702-01, disse que os atores desta campanha vão muito provavelmente armar as credenciais exfiltradas muito depois do comprometimento inicial. Um segredo de vida longa, uma chave de cloud estática, uma chave SSH, um token de publicação, continua utilizável até ser rodado ou revogado. Tanto o FBI como a Aqua dizem a mesma coisa: afastem-se de tokens de vida longa e usem tokens temporários. E isto, mais uma vez, é natural num ambiente que tu controlas de ponta a ponta, onde emites credenciais efémeras, com escopo mínimo, que expiram sozinhas. Num modelo em que despejas todas as chaves de todos os fornecedores num ficheiro de configuração central, estás a construir exatamente o cofre que estes ataques querem arrombar.

Vejam o que aconteceu ao argumento. Ele não é “local é imune”. É “local, feito a sério, esvazia o cofre, tranca a porta de saída e põe validade nas chaves”. Nenhuma dessas três coisas te é dada de borla por correres um container na tua máquina. Mas todas as três só são plenamente tuas para desenhar quando és tu o dono do ambiente. O padrão do gateway de IA na nuvem, ou do gateway self-hosted mal isolado, faz precisamente o contrário: centraliza todas as chaves de todos os fornecedores num sítio só, com saída aberta para a internet por omissão, porque afinal ele precisa de falar com todos esses fornecedores. É um design que normaliza ter o cofre cheio e a porta destrancada. E depois estranhamos quando alguém entra.

O que fazer, agora, se isto te toca

Chega de arquitetura. Vamos ao concreto, que é para isso que este blog existe.

Se há uma hipótese de teres sido tocado por isto, há três coisas imediatas, e não são negociáveis.

Primeiro, verifica se o LiteLLM 1.82.7 ou 1.82.8 esteve instalado em alguma máquina tua durante a janela de auditoria do dia 24 de março, das 10:39 às 16:00 UTC. E lembra-te da lição do ficheiro .pth: não chega perguntares se a tua aplicação importa LiteLLM. Tens de procurar se ele esteve presente no ambiente, mesmo puxado como dependência transitiva por outra coisa. Vasculha os teus lockfiles, o histórico de builds, as imagens de container que construíste nesse período.

Segundo, se encontraste rasto disso, roda tudo aquilo a que essas máquinas tinham acesso. Não “possivelmente”. Tudo. Chaves de API de modelos, credenciais de cloud, chaves SSH, tokens de Kubernetes, passwords de bases de dados, tokens de publicação. Apagar um .env não invalida uma credencial que já saiu pelo fio. Revoga e reemite a sério. E dá prioridade ao que era de vida longa, porque é exatamente isso que o FBI avisa que vai ser usado daqui a meses.

Terceiro, procura nas tuas organizações de GitHub por repositórios chamados tpcp-docs ou docs-tpcp, que o FBI lista como indicadores desta campanha. Atenção a um pormenor traiçoeiro: o malware criava-os com um prefixo tpcp-docs- e carregava os dados roubados como um asset de release com uma tag no formato data-<timestamp>, portanto uma pesquisa por nome exato pode falhar. Procura pelo prefixo, não só pelo nome certinho.

E depois, quando o incêndio estiver apagado, senta-te e faz as perguntas de fundo, que são as que evitam o próximo. Fixaste as versões das tuas dependências, ou andas a instalar o que o índice te der no dia? O teu ambiente de inferência tem saída livre para a internet, ou tem uma allowlist de egress? As tuas credenciais são de vida longa, ou expiram? Tens sequer forma de saber que tráfego sai das tuas máquinas quando um build corre? Se a resposta a estas te deixar desconfortável, ainda bem. O desconforto é o princípio da segurança.

E se me perguntam a mim, sem rodeios: a coisa mais barata e mais reveladora que podes fazer esta semana não é comprar uma appliance de segurança cara. É pôr uma allowlist de egress à frente do teu ambiente de IA e ver o que é que, de repente, deixa de conseguir sair. Vais ter surpresas. Toda a gente tem.

A lição que ultrapassa o LiteLLM

Ia terminar a bater no PyPI e na fragilidade da cadeia de fornecimento de Python, que é fácil e até apetece. Mas seria pequeno parar aí, porque o padrão é maior do que um pacote e um scanner.

Estamos a construir a camada de IA das nossas empresas em cima de uma teia de dependências que ninguém audita por inteiro, com credenciais de fornecedores externos espalhadas por todo o lado, e com máquinas que assumem, por omissão, que podem falar com qualquer ponto da internet. Cada uma destas três escolhas é uma comodidade. Juntas, são um design de exfiltração à espera de acontecer. O TeamPCP não precisou de inventar nada de génio. Precisou de um token esquecido e de infraestruturas que confiam demais e verificam de menos.

A soberania sobre o teu ambiente de IA não é uma bandeira ideológica, por muito que eu goste de a agitar. É uma propriedade técnica muito concreta, e resume-se a controlares três coisas ao mesmo tempo: o que corre na tua máquina, o que esse código consegue ler, e para onde ele consegue mandar. O modelo da nuvem pede-te que confies que a cadeia do fornecedor está limpa. Este incidente é o que acontece quando essa cadeia, o PyPI, o Trivy, um token de publicação, é exatamente aquilo que parte. Correr local não te dá as três coisas de graça. Mas é a única postura em que as podes ter todas.

Por isso o meu recado final não é “abandonem a cloud e corram tudo em casa amanhã”. É mais simples e mais desconfortável. Olhem para a vossa stack de IA e perguntem, honestamente, quantas chaves de terceiros vivem lá dentro que não precisavam de existir, quantos destinos de rede estão abertos que ninguém aprovou, e quantas credenciais continuariam válidas se saíssem hoje pela porta. Reduzam os três. E percebam, de uma vez, que um modelo que é teu, numa rede que é tua, com segredos que expiram e uma porta de saída que tu fechas, não é imune a nada. É só a diferença entre um susto e um comunicado a dizer que 91,7 GB dos teus dados andam por aí.

Fiquem bem, rodem essas chaves, fechem esse egress, e lembrem-se que se não controlam o vosso LLM, e sobretudo a cadeia toda por baixo dele, não controlam nada.

Abraço
Nuno

Fontes e leitura adicional: a peça de partida é a cobertura da The Hacker News sobre as releases maliciosas do LiteLLM; a análise de escala e a ferramenta de consulta pública são da CloudSEK; o relatório de incidente e o aviso de segurança do próprio projeto estão na documentação do LiteLLM; a análise da campanha e do payload é da Unit 42; o contexto do ataque ao Trivy está na Aqua Security; o aviso FLASH do FBI e o registo do CVE-2026-33634 fecham o rasto oficial; e a avaliação da conta comprometida da Comissão Europeia é do CERT-EU.
Este post foi escrito com o auxilio de um LLM privado.

PS: Este ataque comprometeu os módulos referentes a bibliotecas python. Para quem tenha a versão Docker instalada, a menos que tenham instalado exactamente naquela altura, podem estar descansados.