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

A documentação citava um pacote que ninguém possuía

Pesquisadores varreram 6.214 domínios corporativos atrás de llms.txt, o arquivo legível por máquina que as empresas agora publicam para agentes de IA, e encontraram 237 pacotes e domínios citados nesses arquivos que ninguém jamais havia registrado. Eles reivindicaram alguns. O primeiro agente de código dentro de uma rede Fortune 500 instalou um deles em menos de quatro minutos. O atacante não tocou em nenhum mantenedor, nenhum registro e nenhum repositório. A trava de idade do Bromure Agentic Coding já vem ligada em dois dias, e um nome que um atacante reivindica é, por construção, uma publicação novinha em folha.

Um fornecedor escreveu na própria documentação um comando de instalação para um pacote que nunca publicou. O nome ficou ali, livre, à disposição de quem quisesse. Os agentes de código leram a doc, acreditaram nela e rodaram a instalação.

Em algum momento dos últimos dois anos, suas ferramentas de desenvolvimento passaram a distribuir um arquivo novo. Ele fica ao lado do robots.txt, chama-se llms.txt e guarda um resumo em texto puro do produto, escrito para uma máquina e não para uma pessoa. O que a API faz. Quais endpoints importam. Qual pacote instalar para começar. Os agentes de código o buscam e, como ele vem do domínio do próprio fornecedor, tratam-no como fato consumado.

Um pesquisador chamado Alon Hertz, que trabalha sob o nome PANDEX, foi ver o que esses arquivos mandam um agente fazer. Seu método tem três passos e nenhum exploit: puxar llms.txt e llms-full.txt de diretórios de empresas, rodar agentes de verdade contra eles em sandboxes e observar o que os agentes buscam, depois conferir cada nome de pacote e cada hostname que saiu dali no npm, no PyPI e nos registros de domínio. Ele descreve o ferramental em whatwouldai.do, sob um lema que acabou virando a descoberta: agentes agem sobre qualquer coisa.

A varredura cobriu 6.214 domínios ativos, tirados de empresas da Fortune 500, big techs, fintechs e empreiteiras de defesa, e mais de 8.000 desses arquivos. Em 120 deles, aquilo que o arquivo mandava o agente instalar não existia. No corpus inteiro, 237 nomes de pacotes e hostnames citados estavam livres: disponíveis no npm, PyPI, RubyGems, NuGet, crates.io e Packagist, ou livres como subdomínios na Vercel, na Render, na Fly e na Netlify. Qualquer um podia tomá-los pelo preço de um cadastro.

Então os pesquisadores pegaram um punhado, publicaram pacotes inofensivos sob esses nomes com um beacon que ligava para casa na instalação, e esperaram.

O primeiro retorno chegou em menos de quatro minutos. O segundo veio dentro da hora. Ao longo do período seguinte, registraram instalações vindas de dezenas de organizações, várias delas ambientes Fortune 500, disparadas por agentes rodando sobre o Claude da Anthropic, o Codex da OpenAI e o Hermes da Nous Research. Bruce Schneier pegou o estudo em 4 de setembro e o leu como merece: a linha entre dados e código executável está desmoronando. Um pesquisador citado nesse post usa menos palavras. O modelo de confiança está quebrado. Os agentes tratam a documentação do fornecedor como verdade absoluta e não a questionam.

A Clerk documentou um pacote que nunca entregou

O exemplo mais claro vem de um fornecedor de autenticação, cuja documentação manda o leitor rodar:

npx clerk-next-fix-auth-protection

A Clerk nunca publicou um pacote autônomo com esse nome. Talvez o comando fosse um atalho, talvez apontasse para algo interno, talvez um redator tenha errado. O motivo não importa, porque o nome soava real para um agente. Outra pessoa o registrou. Bases de advisories agora listam esse pacote como MAL-2026-11069, e ele fez o que se constrói para reconhecimento: os hooks de instalação do npm dispararam e mandaram embora o nome da conta que instalava, o hostname da máquina, o diretório de trabalho e um timestamp.

Isso é um mapa do ambiente de build de alguém, coletado dentro da rede dessa pessoa pelo agente de código dela mesma, que estava seguindo a documentação do fornecedor dela mesma.

Cinco passos comuns, nenhum comprometimento na cadeia1 · o fornecedor publicavendor.com/llms.txtescrito para agentes, não pessoasnpx clerk-next-fix-auth-protection2 · o nome está livrenenhum pacote foi publicadocom esse nome237 nomes assim em 120 arquivos3 · alguém o registrauma publicação npm comumMAL-2026-11069a carga vive nos hooks de install4 · o agente lê a doc e acredita nelaClaude, Codex e Hermes foram vistos rodando a instalaçãoo comando veio do domínio do próprio fornecedor, não havia o que duvidarsem credencial roubada · sem repositório comprometido · sem mantenedor phishado5 · os hooks ligam pra casausuário · hostnamediretório de trabalho · timestampum mapa do ambiente de buildO único trabalho real do atacante foi ler um arquivo que o fornecedor da vítima publicou de propósito.
A cadeia não tem nenhum comprometimento dentro dela. Um fornecedor publica uma doc legível por máquina citando um comando de instalação; o pacote nunca foi publicado, então o nome continua livre no registro; um atacante o registra; o agente busca a doc como verdade absoluta e roda a instalação; os hooks de instalação relatam o ambiente de volta. Cada elo é uma operação normal executada corretamente.

Por que o conselho de sempre aponta direto para a carga

Todo guia de cadeia de suprimentos manda você conferir o nome de um pacote na documentação oficial. Esse conselho falha aqui, porque a documentação oficial é justamente de onde veio o nome ruim.

Inverta o problema sobre o qual escrevemos em julho e você chega a este. Lá, um agente alucina um nome de pacote plausível e um atacante registra os nomes que ele tende a inventar, então o elo fraco é o palpite do modelo. Aqui o modelo não dá palpite nenhum. Ele vai até o fornecedor, lê o que o fornecedor diz, segue aquilo, e é essa sequência cuidadosa que o pega.

Um comprometimento de registro também tem outro formato. Quando um workflow de release publicou dez versões envenenadas de um pacote real no mês passado, algo tinha que quebrar primeiro: um workflow que confiou em um comentário. Aqui nada quebra. Um redator técnico digita um comando que não bate exatamente com um artefato publicado, que é o erro mais comum do software, e a lacuna entre o nome e o artefato vira uma primitiva de execução que qualquer um na internet pode pegar do chão.

O que a varredura achou, e a rapidez da prova6.214domínios ativos varridosFortune 500, big tech, defesa8.000+docs voltadas a agentesllms.txt · llms-full.txt120arquivos citando algoque nunca foi registrado237nomes livres, 6 registrosmais Vercel, Render, Fly, Netlifyentão registraram uma amostra e esperaramprimeiro retorno de instalação de um ambiente Fortune 500menos de 4 minutossegundo retornodentro da hora · dezenas de organizações
O funil do estudo. De 6.214 domínios corporativos com documentação voltada a agentes, 120 arquivos citavam algo não registrado, somando 237 nomes livres. Registrar uma amostra deles produziu um retorno de instalação vindo de dentro de uma rede Fortune 500 em menos de quatro minutos.

A camada que pega isso já está ligada

O Bromure Agentic Coding roda cada agente de código em uma VM Linux virtualizada por hardware no seu Mac, e todo pacote que o agente busca passa por um proxy do lado do host dessa fronteira, antes que um byte chegue à VM. Várias camadas se aplicam ali. Para esta história, uma delas faz o trabalho sozinha, e é justamente a que um workspace novo já traz ligada.

A trava de idade faz de um nome recém-reivindicado um não-evento. Um workspace recusa versões de pacote mais novas que um corte, definido em dois dias de fábrica. O proxy aplica isso reescrevendo a listagem de versões do registro na passagem: remove as versões mais novas que o corte e reaponta latest para a mais nova sobrevivente. Um pacote publicado pela primeira vez hoje de manhã não tem nenhuma versão velha o bastante para sobreviver a esse filtro, então, do ponto de vista do agente, não há nada naquele nome para instalar. Se o agente pedir diretamente uma versão nova demais, a busca volta como um HTTP 451 cujo corpo informa a idade real do pacote e o mínimo exigido, e o npm imprime esse texto tal e qual.

Leia o ataque de novo com isso no lugar. A técnica inteira depende de um nome passar de não publicado a publicado num momento escolhido pelo atacante. Essa transição é um release novinho em folha. A única propriedade de que o atacante não consegue fugir é exatamente aquela em que a política padrão se apoia.

A remoção de scripts de instalação elimina justamente o mecanismo que a carga usou. O pacote observado fazia seu trabalho nos hooks preinstall, install, postinstall e prepare do npm. Com a remoção de scripts ligada, o proxy descompacta cada tarball do npm em voo, apaga essas quatro chaves do package.json, limpa dist.integrity e dist.shasum dos metadados para que a própria verificação de hash do npm continue passando, e recompacta. O pacote instala; o beacon não tem de onde rodar. Pacotes que precisam dos seus hooks, como compiladores de bindings nativos tipo better-sqlite3, entram numa lista de permissão por workspace e os mantêm.

A filtragem por reputação conhece esse pacote pelo nome. MAL-2026-11069 é um advisory de malware. Com o socket.dev selecionado como filtro de pacotes do workspace, seu bloqueio de pacotes comprometidos dispara em sinais de malware e de typosquat em qualquer severidade; a checagem gratuita do OSV cobre os oito ecossistemas interceptados sem chave de API. Quando uma fonte de reputação não consegue produzir um veredito, seja por rate limiting, por rede caída ou por um ecossistema que ela não cobre, o Bromure falha fechado em vez de liberar a busca. O download pausa e pergunta a você, pacote a pacote e versão a versão, de modo que um atacante capaz de induzir uma falha de consulta não ganha nada.

O reconhecimento que ele coleta pertence à VM. Toda manchete sobre este estudo diz a mesma coisa: agentes estão instalando código desconhecido em redes corporativas. A colheita do beacon foi o nome de usuário que instalava, o hostname da máquina e o diretório de trabalho. Num workspace Bromure, esses três campos descrevem um convidado Linux no seu Mac, e não um host de build ligado ao domínio com um nome interno que vale a pena entrar numa lista de alvos. As credenciais que ficam nessa VM são chamarizes cujos valores reais só existem na memória do proxy do host. Se um código hostil tentar levar uma delas a um host para o qual ela não foi cunhada, o proxy bloqueia a requisição e pausa a VM em pleno voo.

A negação padrão responde à outra metade do estudo. Boa parte dos nomes livres eram hostnames, não pacotes: domínios expirados e subdomínios gratuitos na Vercel, na Render, na Fly e na Netlify, sentados em docs que os agentes vão buscar. O firewall de saída do workspace é uma tabela ordenada de regras com um ajuste Unmatched traffic; coloque-o em Deny e a VM abre conexões para os hosts que você listou e para mais nada, em qualquer protocolo. Dois componentes impõem isso fora do convidado: o switch de rede virtual casa cada fluxo pelo IP de destino, e o proxy do host casa de novo pelo nome de servidor TLS. Um beacon mirando um domínio que alguém registrou na terça passada não sai da máquina. Edições de regra alcançam sessões em execução sem reboot.

Toda tentativa ganha uma linha. No nível de trace Activity only, o proxy escreve um registro de metadados por requisição que sai da VM, carregando timestamp, host, porta, método, caminho, status, latência e bytes, e não guarda nenhum corpo de requisição. bromure-cli trace hostnames my-workspace imprime cada host distinto que o workspace contatou, com contagens. Você vê um domínio de retorno que nenhum dos seus engenheiros reconhece no dia em que acontece, na sua própria máquina, em vez de ler sobre ele no conjunto de dados de outra pessoa meses depois.

Um agente numa máquina de devnpx clerk-next-fix-auth-protectiondireto da doc do próprio fornecedoro que vem depois, tudo dentro da redeo registro serve o que foi publicado, por mais novo que sejapostinstall roda com a identidade do desenvolvedorusuário, hostname e cwd são fatos corporativoso retorno vai para onde bem entendernenhum registro por requisição do destinoa instalação dá certo e parece igual a todas asoutras que o agente rodou naquela tardePrimeiro retorno: menos de quatro minutos.Você fica sabendo quando um estranho publica o estudo.Um workspace Bromure Agentic Codingnpx clerk-next-fix-auth-protectiono mesmo comando, da mesma documentaçãoo que vem depois, tudo no hosttrava de idade: versões com menos de dois dias saemum nome tomado hoje não tem nada instalávelstrip: os quatro hooks de instalação são removidosusuário, hostname e cwd descrevem uma VM Linuxsaída sem correspondência negada, por IP de destinoa recusa é um 451 legível pelo agente, e uma linhano Security Log com a idade real do pacotePrimeiro retorno: nenhum.Você fica sabendo pela sua máquina, no mesmo dia.
À esquerda: a instalação roda numa máquina de desenvolvimento dentro da rede corporativa, o registro é consultado direto, os hooks de instalação executam com a identidade do desenvolvedor, e o retorno parte sem deixar registro. À direita: o mesmo comando, passando por um proxy do lado do host que filtra a versão recém-publicada para fora dos metadados, remove os hooks se chegar até lá, nega a saída sem correspondência, e escreve uma linha para cada requisição.

O que já está ligado

Um workspace novo vem com a trava de idade habilitada num mínimo de dois dias, e é essa a configuração em que este ataque esbarra. Aumente-a se o seu trabalho tolera a espera, até 90 dias no seletor, e use Exempt packages para aqueles que a sua própria equipe publica e instala na mesma hora, na forma npm:nosso-pacote.

O que vale a pena ligar hoje

No painel Supply Chain do workspace, habilite a remoção de scripts de instalação e escolha um filtro de pacotes. Em Guardrails, coloque Unmatched traffic em Deny e liste os hosts de que o trabalho precisa. Em Tracing, Activity only guarda a trilha de hosts contatados e nada do conteúdo. Cada uma dessas edições vale para sessões que já estão rodando.

Ninguém revisa um arquivo escrito para máquinas

O scanner do PANDEX caça mais uma coisa. Ao lado de pacotes e domínios não reivindicados, ele procura instruções dirigidas ao agente e não ao leitor: diretivas plantadas no arquivo, às vezes escondidas com truques de Unicode e caracteres de largura zero. O pipeline de build de um fornecedor gera o llms.txt, o seu agente o consome, e ninguém em nenhuma das duas empresas lê o que passou entre elas.

O Bromure trata uma página buscada como entrada não confiável. O conteúdo que o agente puxa de fora, seja conteúdo de arquivos, páginas web, corpos de issues ou saída de comandos, chega ao modelo como um span tool_result. Com o detector de código-fonte habilitado, um modelo PromptGuard rodando no seu Mac pontua esses spans no fio antes que o modelo aja sobre eles, e você escolhe se ele registra, pergunta ou bloqueia. Nenhum conteúdo vai a lugar nenhum para ser analisado.

Passamos uma década aprendendo a verificar artefatos: assine o pacote, fixe o hash, ateste o build. Este estudo trata do passo anterior a tudo isso, aquele em que um agente decide qual artefato pedir, trabalhando a partir de um documento que não carrega assinatura nenhuma e foi escrito por um redator técnico com pressa. A documentação virou uma entrada executável, e os registros estão cheios de nomes livres esperando que alguém repare que um fornecedor os mencionou.

Passe um grep no seu próprio llms.txt atrás de nomes que você nunca publicou. Isso é trabalho de uma manhã e tira você da lista de alvos de alguém. Depois dê ao seu agente uma máquina em que um pacote publicado há quatro minutos não é instalável, e onde você consegue ver todos os lugares aonde ele foi. Instale o Bromure Agentic Coding e ponha o caminho de instalação no host.