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

O scanner recusou-se a lê-lo

O Threat Intelligence Group da Google publicou o seu AI Threat Tracker a 8 de setembro. Enterrada na secção sobre um grupo de comprometimento da cadeia de fornecimento chamado UNC6780 está uma técnica que transforma a política de segurança de um modelo em cobertura: o carregador JavaScript do malware abre com um bloco de comentários a exigir rotas de síntese de armas biológicas e especificações de armas nucleares, colocado ali para que um scanner de segurança baseado em LLM recuse por política e nunca chegue ao código por baixo. A recusa é a evasão. O Bromure Agentic Coding responde a artefactos destes com verificações que nunca os leem.

Convencer um scanner de segurança a aprovar malware dá trabalho. Entregar-lhe um ficheiro tão radioativo que ele se recusa a olhar é um bloco de comentários, e o seu pipeline arquiva a recusa como análise indisponível.

O seu pipeline de build já tem um LLM lá dentro. A maioria tem. Algures entre a chegada da atualização de dependência e o acender do botão de merge, um modelo lê o diff, lê os ficheiros novos e escreve uma opinião curta sobre se algo daquilo parece hostil. Bom uso da tecnologia: apanha carregadores ofuscados por onde a correspondência de padrões passa ao lado, e custa uma fração de cêntimo por ficheiro.

A 8 de setembro, o Threat Intelligence Group da Google publicou o seu AI Threat Tracker, um balanço trimestral do que os atacantes andam a fazer com isto. A maior parte da cobertura foi para o número de manchete: um agente com motivação financeira que comprometeu a infraestrutura cloud de uma empresa e depois, nas palavras do GTIG, “aproveitou um chatbot de programação com IA, um prompt e um conjunto de instruções de agente para planear, construir e executar uma campanha de recolha massiva de credenciais em menos de seis horas”. Milhares de credenciais de terceiros, encaminhadas para fora pelos próprios endereços IP da vítima. Num servidor exposto separado, os investigadores encontraram uma framework de reconhecimento cujo painel acompanhava em tempo real mais de 23 800 segredos recolhidos.

Foi essa a história que se escreveu. O parágrafo que merece a sua tarde está várias secções mais abaixo, na parte sobre um grupo de cadeia de fornecimento que a Google segue como UNC6780.

O comentário no topo do ficheiro

O UNC6780, que também atende por TeamPCP, tem conduzido comprometimentos de open source em larga escala desde março de 2026 no PyPI, no npm e no Docker Hub. O seu ladrão de credenciais atual chama-se DUSTMAKER, e é uma payload JavaScript. Payloads JavaScript têm um carregador. O deste é um ficheiro chamado _index.js, e o GTIG encontrou algo novo no topo dele:

Além de prompts embutidos em ficheiros de configuração dirigidos a agentes de programação com IA, as amostras de DUSTMAKER incluíam prompts embutidos como comentários no topo dos scripts carregadores JavaScript do malware (_index.js). Estes prompts contêm texto adversarial extremo, referindo temas como armas biológicas e nucleares, provavelmente com a intenção de fazer falhar scanners de segurança baseados em LLM ou de os levar a saltar a análise do código JavaScript malicioso por baixo, devido a recusas de segurança ou de política.

O bloco de comentários deixa o malware à vista de todos. Desempacotado, sem codificação, num único ficheiro: o JavaScript malicioso está três linhas mais abaixo, onde qualquer pessoa que lesse até ao fim tropeçaria nele.

O comentário torna o leitor pouco disposto a chegar ao fim. Abre com uma falsa anulação de sistema, anuncia que as diretrizes de segurança estão suspensas, e exige uma rota de síntese passo a passo para armas biológicas dispersas em aerossol. Depois passa ao desenho de uma arma nuclear de implosão, com alvo de potência incluído. Um modelo que ingere o ficheiro e a quem se pergunta “este código é malicioso?” passa a ter à frente uma pergunta mais barulhenta, e é essa que responde, com uma recusa. O scanner devolve uma mensagem de política. O seu pipeline regista análise indisponível, como faz umas dezenas de vezes por semana para timeouts e ficheiros demasiado grandes, e o build fica verde.

A técnica não custa nada ao UNC6780. Não exige conhecimento nenhum sobre que modelo lê o ficheiro, que fornecedor aloja esse modelo, ou o que diz o seu prompt de sistema. Pede uma coisa ao leitor: uma política de segurança, que num pipeline de garantia é a razão pela qual escolheu um modelo logo à partida.

A recusa é a evasão_index.js/* SYSTEM OVERRIDE — CLASSIFIED BRIEFINGrota de síntese de arma bioespecificação de engenho de fissãoconcebido para ser recusadoconst _0x = require(…)o ladrão de credenciaisem texto simples, sem ofuscaçãonunca alcançadolê a partir do toposcanner de segurança LLMpergunta: este código é hostil?responde antes à mais barulhentarecusa de políticao log de buildanálise indisponívela mesma linha que imprimepara timeouts e ficheiros grandeso que o atacante teve de sabernem que modelo lê o ficheiro · nem que fornecedor o alojanem o prompt de sistema · nem o limiarapenas que o leitor tem uma política
O carregador do DUSTMAKER não esconde a sua payload. Abre com um bloco de comentários concebido para disparar a política de segurança do modelo que o examina, de modo que o modelo recusa o ficheiro inteiro e o JavaScript malicioso três linhas abaixo nunca é avaliado. Uma recusa e um atestado de saúde são idênticos para um log de build que só verifica se o passo falhou.

O resto da cadeia faz o mesmo

Assim que se vê a jogada, o resto do ofício do UNC6780 lê-se como variações dela. Cada defesa restante no caminho é também um leitor a formar um juízo sobre um artefacto que o atacante escreveu, e cada uma é alimentada em vez de derrotada.

O pacote é entregue pelas ferramentas em que o agente mais confia. O GTIG descreve o UNC6780 a comprometer contas legítimas de programadores “para publicar forks trojanizados de servidores MCP legítimos no registo PyPI, como tiktoken_mcp, e injetar código malicioso diretamente em repositórios GitHub oficiais de organizações, como azure-functions-mcp-extension”. Pôr backdoors em servidores MCP significa que a payload chega pela única classe de dependência que um agente de programação é feito para carregar e à qual concede ferramentas.

A verificação de proveniência recebe uma assinatura válida. Quem trate atestações como sinal de confiança devia parar nesta:

As amostras de DUSTMAKER contêm funcionalidade para detetar quando estão a correr num ambiente de integração e entrega contínuas (CI/CD). Confirmando-o, extraem tokens OIDC da memória de processos dos runners do GitHub Actions. Com esses tokens, o DUSTMAKER autoriza-se a si próprio como publicador de confiança e publica versões comprometidas de pacotes com atestações SLSA Build 3 válidas e assinadas criptograficamente. Pacotes publicados com tokens válidos passarão as verificações de confiança automatizadas dos agentes de programação com IA.

Foi a identidade do próprio publicador que assinou aquele pacote. O DUSTMAKER levantou a identidade da memória de processos no runner onde vive, pelo que um verificador que confira a assinatura devolve válida, e tem razão.

O agente de endpoint vigia o bairro errado. O DUSTMAKER “larga ou modifica ficheiros maliciosos em diretórios ocultos de workspace de projeto para assistentes de programação com IA e ambientes de desenvolvimento integrados (IDEs) (.claude/, .vscode/, .cursor/, etc.)”. O GTIG diz porquê: essas pastas deixam o malware fundir-se “no ruído rotineiro do programador” e afastar-se dos sítios que a deteção de endpoint vigia, como chaves de registo e /etc/cron.*. O seu fornecedor de EDR não lançou uma regra para um ficheiro novo em .cursor/, porque até este ano aquela pasta guardava preferências do editor.

O assistente recebe um ficheiro de configuração, e ficheiros de configuração são instruções. A partir desses mesmos diretórios ocultos, o DUSTMAKER “usa ficheiros de configuração maliciosos … para instruir o assistente de IA a executar comandos ou scripts arbitrários (como setup.mjs) durante interações rotineiras de desenvolvimento. Isto força efetivamente o modelo de IA a executar comandos em nome do atacante sem o conhecimento do programador”. O assistente leu a configuração do seu workspace, que é o seu trabalho, e a configuração nomeou um script para correr. Nenhum exploit em toda essa frase.

E a API de auditoria apaga a trilha de auditoria. O malware disfarça as suas tarefas de CI com nomes de tema IA como “Copilot Setup”, e depois emite chamadas automatizadas à API para remover os logs de execução de workflows da interface do GitHub.

Cinco leitores, cinco artefactos escritos pelo atacanteO LEITORO QUE LHE ENTREGARAMO QUE CONCLUIUscanner de segurança LLMlê o ficheiroum bloco de comentários que tem de recusararmas biológicas, depois nuclearesnada — declinouregistado como análise indisponívelverificador de proveniêncialê a assinaturauma atestação SLSA Build 3 realcunhada com um token OIDC roubadoválida, e com razãoa identidade era mesmo a do publicadorassistente de programação IAlê a config do workspaceuma config escrita pelo atacante.claude/ · .cursor/ · .vscode/executar setup.mjsdurante uma interação de rotinadeteção de endpointvigia onde o malware escreveescreve antes numa pasta do editornem chaves de registo, nem /etc/cron.*ruído rotineiro do programadorainda não há regra para esse caminhoo auditorlogs de workflow apagados pela API do GitHubum histórico vazio
Cinco defesas numa só cadeia de ataque, e nenhuma delas avariou. Cada uma é um leitor que forma um juízo sobre um artefacto, e em todos os casos foi o atacante que escreveu o artefacto. O scanner recusou, o verificador verificou, o assistente obedeceu, o agente de endpoint olhou para outro lado, e o auditor encontrou um log vazio.

As cinco funcionaram como projetado

Não tem bug nenhum para reportar aqui. O scanner aplicou a sua política de segurança, que é aquilo por que lhe paga. O verificador validou uma assinatura que era válida. O assistente carregou a configuração do seu workspace, que é a funcionalidade. O agente de endpoint vigiou os caminhos que importavam antes de os agentes de programação terem diretórios de configuração. Cada um fez o seu trabalho sobre a entrada que recebeu, e foi o UNC6780 que escolheu a entrada.

A evasão comum pede um veredicto errado: empacotar o binário, partir a string, codificar o URL, esperar que a análise volte limpa. O DUSTMAKER consegue o que quer de um veredicto certo, ou de veredicto nenhum.

Toda a defesa com a forma ler o artefacto, formar uma opinião está ao alcance disto. Um modelo de linguagem na cadeira do leitor alarga esse alcance, porque um modelo carrega a sua própria lista de coisas que não fará, e essa lista é pública, documentada e acessível a quem saiba escrever um comentário. A indústria passou dois anos a endurecer modelos contra serem convencidos a dizer sim. O UNC6780 convence-os a não dizer nada.

Faça então outra pergunta aos controlos do seu pipeline agêntico: quais deles têm de ler conteúdo escrito pelo atacante para fazer o seu trabalho, e o que fica de pé depois de pôr esses de lado.

Verificações com que não se discute

O Bromure Agentic Coding corre cada agente de programação dentro de uma VM Linux virtualizada por hardware no seu Mac, com todos os controlos de segurança do lado do anfitrião dessa fronteira. O que conta contra uma cadeia como esta é o que esses controlos tomam como entrada.

O portão de idade lê um relógio. O proxy do anfitrião reconhece pedidos aos grandes registos de pacotes e aplica a política de cadeia de fornecimento do workspace antes de um byte chegar à VM. O portão de idade, a única camada ligada por omissão e definida num mínimo de dois dias, recusa versões mais novas do que o corte. Reescreve para isso a listagem de versões do registo, pelo que do ponto de vista do agente uma versão demasiado fresca ainda não existe; uma obtenção direta fixada volta como HTTP 451 cujo corpo indica a idade real do pacote. O pacote em si nunca é lido. Um bloco de comentários concebido para descarrilar um modelo de linguagem não move nenhuma data de publicação, e os forks trojanizados do UNC6780 são publicações frescas por construção. npm, PyPI, Cargo, RubyGems e Packagist trazem todos datas de publicação por versão, por isso o portão cobre-os.

Um classificador não tem política a invocar. O detetor de código-fonte do Bromure pontua os trechos tool_result que o agente devolve em streaming ao modelo — conteúdos de ficheiros, páginas web e saída de comandos — com um modelo PromptGuard local da família DeBERTa a correr como ONNX no seu Mac. É um classificador de sequências e não um modelo generativo. Entregue-lhe o bloco de comentários do DUSTMAKER e ele não compõe resposta nenhuma, não pesa nenhuma questão sobre se responder é permitido, e não declina nada. Emite um número entre zero e um, e texto que abre com uma falsa anulação de sistema a anunciar diretrizes de segurança suspensas é exatamente a forma que faz subir esse número. Por workspace, escolhe o que um acerto faz: registar, perguntar ou bloquear. Um bloqueio devolve 451, e o modelo nunca vê o conteúdo.

Outra coisa que não o agente lê a configuração do workspace. Um ficheiro em .claude/ a dizer ao assistente para correr setup.mjs é a backdoor por ficheiro de regras, e tem o seu próprio detetor: uma passagem heurística determinista para Unicode oculto, padrões de meta-instrução, caminhos de credenciais e construções curl-para-shell, mais um classificador ModernBERT afinado para as instruções maliciosas que não batem com padrão fixo nenhum. Ambos correm no proxy, no dispositivo, sobre os ficheiros de instruções extraídos do prompt de sistema, onde nada dentro da VM os pode desligar. Ambos são interruptores por workspace, e cada um quer um download de modelo na primeira vez que o liga. Esse download são os cinco minutos que esta história lhe pede.

Um ladrão de credenciais precisa de credenciais. O DUSTMAKER existe para colher segredos em ambientes de desenvolvimento, e um workspace Bromure não tem nenhum. O Bromure substitui cada credencial que configura por um falso que preserva a estrutura, derivado do valor real e de um sal por instalação: sk-ant-api03-brm-…, um token ghp_ do comprimento certo, brm-mcp_…, brm-k8s-…. Esses falsos vão para as variáveis de ambiente e para ~/.git-credentials, ~/.docker/config.json, ~/.kube/config e ~/.aws/config, que é a lista que um ladrão enumera. Os seus valores reais ficam cifrados no Mac, e o proxy do anfitrião troca-os na linha, limitados ao anfitrião de destino para que cada um foi cunhado. Os bytes das chaves privadas SSH nunca entram na VM; só as assinaturas atravessam. Nenhum interruptor governa nada disto, porque o proxy é a única rota da VM para a rede.

Os falsos servem também de fios-armadilha. Um token falso tem um único destino legítimo. O proxy varre cada pedido de saída, cabeçalhos e corpo, à procura de um falso a caminho de outro sítio. Ao encontrar um, recusa o pedido sem encaminhar um byte, põe a VM em pausa ali mesmo, e regista uma linha vermelha de Credential brokering na Security Timeline. Marca o workspace como comprometido, e o arranque seguinte obriga-o a limpar primeiro o disco e a imagem de home. Não roda nada depois, porque o ladrão levava um marcador de lugar e o marcador nunca saiu.

O registo vive na sua máquina, fora do alcance da API que o malware chama. Com o tracing em Activity only, o proxy escreve uma linha de metadados por pedido que sai da VM, sem corpos. bromure-cli trace hostnames imprime cada anfitrião distinto que o workspace contactou com contagens, e bromure-cli trace leaks nomeia o destino depois de um alerta de comprometimento. Um atacante com um token do GitHub esvazia um histórico de workflows em meia dúzia de chamadas à API. Esvaziar aquele registo implica chegar ao seu Mac.

controlos que leem o artefactoo atacante escreve a entrada de cada um deleso modelo lê o ficheirodê-lhe algo que ele tenha de recusaro verificador lê a assinaturaroube o token OIDC; a assinatura é realo assistente lê a configescreva a config; ele corre o que nomearo ambiente guarda segredos reais~/.aws/config · ~/.docker · GH_TOKENo que o atacante precisaum bloco de comentários e um leitor com políticacontrolos que leem outra coisano anfitrião, fora da VM, no seu Maco portão de idade lê uma data de publicaçãodois dias por omissão; o demasiado fresco não existeo classificador emite uma probabilidadeONNX local · registar, perguntar ou bloquear · nada a recusara VM só tem marcadores de lugarsk-ant-api03-brm-… · brm-mcp_… · brm-k8s-…o proxy compara bytes com um destinomarcador fora do âmbito: 451, VM em pausa, linha vermelhao que o atacante precisamudar uma data, ou um segredo que não está lá
O mesmo artefacto contra dois tipos de verificação. Um controlo que lê conteúdo escrito pelo atacante pode receber conteúdo concebido para o descarrilar. Um controlo que lê uma data, um padrão de bytes ou um anfitrião de destino não tem nada com que discutir, e a credencial que o ladrão veio buscar nunca esteve na máquina onde ele corre.

Duas definições, esta tarde

Abra o painel Supply Chain do workspace e confirme que o portão de idade está ligado com um corte com que consiga viver. Dois dias é a omissão, e é o controlo mais barato que tem contra um fork trojanizado acabado de publicar. Depois abra Prompt Injection e ative os dois detetores; cada um descarrega o seu modelo uma vez, e daí em diante a análise é local e gratuita.

Uma regra que encolhe a pergunta

Em Guardrails, ponha Unmatched traffic em Deny e liste o que o trabalho precisa: allow web api.github.com, allow web registry.npmjs.org, default deny. Um ladrão que não consegue chegar ao seu servidor de comando é um ficheiro num disco que está prestes a deitar fora, e gravar empurra a regra para as sessões em curso sem reinício.

A ausência vence a avaliação

Continuamos a chegar ao mesmo sítio por caminhos diferentes. Um proxy de saída que confiou num nome que o agente podia escrever. Uma guarda de comandos que lia bash de forma diferente do bash. Um gateway de IA cuja verificação falhada de chave caía num objeto de autenticação vazio. Em cada caso um componente fazia um trabalho honesto de avaliar alguma coisa, e a avaliação partiu-se.

O DUSTMAKER afia o argumento, porque não pede avaliação partida nenhuma. Pede que uma avaliação aconteça sobre conteúdo que o UNC6780 escreveu. Dê um ficheiro a um leitor e o leitor forma uma opinião sobre ele; quem forneceu o ficheiro decide o resto.

A avaliação é uma primitiva mais fraca do que a ausência, e não paramos de entregar à avaliação o trabalho que a ausência faz de graça. Não se discute com uma data de publicação, e uma probabilidade não tem vontade nenhuma de se retratar. Um ladrão de credenciais a correr com privilégios totais dentro de uma VM descartável, a percorrer todos os ficheiros de configuração que conhece, sai de lá com um punhado de marcadores de lugar e um socket que não consegue contornar.

Pôr um modelo no seu pipeline de revisão foi a decisão certa. Garanta que os controlos que travam as coisas são aqueles com quem nada no repositório consegue falar. Instale o Bromure Agentic Coding e dê ao seu agente verificações que nunca têm de ler o ficheiro do atacante.