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

Ninguém publicou uma nova versão

O comunicado da Wordfence de 9 de agosto sobre o comprometimento da BdThemes descreve um ataque à cadeia de suprimentos sem cadeia de suprimentos nenhuma. Nenhum lançamento, nenhum script de instalação, nenhum typosquat, nenhum arquivo modificado no repositório do plugin. Os atacantes obtiveram acesso de escrita ao bucket de armazenamento do fornecedor e alteraram um feed JSON que 350.000 sites buscavam a cada carregamento de página do painel administrativo. Para quem desenvolve, a pergunta que isso levanta é o que a máquina que roda o seu agente de código tem permissão de escrever, e para quem.

Todo controle de cadeia de suprimentos que você possui inspeciona coisas que foram publicadas. Este não publicou nada, e mesmo assim alcançou 350.000 sites.

Em 7 de agosto de 2026, a Wordfence começou a ouvir relatos de sites WordPress rodando plugins da BdThemes onde brotavam contas de administrador que ninguém havia criado. A lista chega a sete plugins: Element Pack Addons for Elementor, Prime Slider, Pixel Gallery, Ultimate Post Kit, Ultimate Store Kit, Live Copy Paste e Smart Admin Assistant. Só o Element Pack passa de 100.000 instalações ativas, e os sete juntos ultrapassam 350.000.

Em 8 de agosto o fornecedor já havia limpado sua API e o WordPress.org já havia retirado todos os sete plugins enquanto durasse a investigação. A Wordfence publicou seu PSA sobre o comprometimento no dia seguinte, e sua conclusão central é uma negativa: ninguém havia tocado no código dos plugins.

Nada foi publicado

Os plugins da BdThemes carregam um componente interno chamado Biggopti cujo trabalho é pequeno e monótono: buscar banners promocionais da API remota do fornecedor e desenhá-los no painel administrativo do WordPress. Os dados do banner são um arquivo JSON estático em um bucket do DigitalOcean Spaces, servido por trás da Cloudflare.

Os atacantes conseguiram acesso de escrita a esse bucket. Essa é toda a intrusão.

O Biggopti pegava o campo display_id da resposta JSON e o soltava dentro de um atributo HTML id sem escapá-lo, uma falha de cross-site scripting que a Wordfence classificou como CVSS 5.4, média e rastreou até 1º de março de 2026, quando um desenvolvedor a escreveu uma vez e a copiou de um plugin para o seguinte. Um display_id carregando um manipulador onanimationstart é, portanto, JavaScript, e ele roda no navegador de todo administrador logado, a cada carregamento de página do wp-admin, em todo site com o plugin instalado.

A carga usou bem esse assento. Um script chamado w2.js pedia instruções de alvo a um servidor de comando e controle, a máquina que um atacante usa para dizer a uma carga quais sites valem a pena tomar, e então criava contas de administrador ocultas pela API REST do WordPress, montado na própria sessão e nos nonces do administrador de verdade. Uma variante derivava nomes de usuário previsíveis do nome de host do site. Um plugin falso, muitas vezes chamado de algo como wp-smart-thumbnails, entregava um web shell em emer-run.php, ou seja, um script que um atacante consegue pilotar carregando uma URL comum. Must-use plugins, que o WordPress carrega a cada requisição e esconde da tela de plugins, carregavam um backdoor de login mágico e um módulo que ocultava as contas novas reescrevendo os totais de usuários que a tela administrativa exibe.

Os carimbos de tempo nos registros envenenados colocam o início mais antigo possível em 23 de junho de 2026, sete semanas antes de alguém perceber.

Alinhe isso com os controles que uma equipe cuidadosa mantém. Fixação de versão: as versões nunca mudaram. Lockfiles e hashes de integridade: batiam, porque os arquivos que eles cobriam estavam intocados. Revisão de script de instalação: não houve instalação. Detecção de typosquat: nome certo, fornecedor certo, entrada certa no repositório. Revisão de código do diff entre lançamentos: nenhum diff, porque nenhum lançamento. Certificado e CDN: ambos em perfeita saúde. Cloudflare e TLS provaram que o JSON chegou ao site sem modificação, vindo do bucket de onde deveria vir, que é precisamente o que o tornava útil.

O caminho que todos vigiamWordPress.orgsete plugins, 350.000+instalações ativaso que os controles veemversões, hashes, scripts deinstalação, o diff do releaseveredito: limpoe correto. Nenhum arquivo dorepositório foi jamais modificado.O caminho que ninguém vigiavao bucket do fornecedorDigitalOcean Spaces,atrás da Cloudflarealguém podia escrever neleBiggopti busca o bannerdisplay_id entra num atributoid HTML sem escaparonanimationstart o executana sessão do próprio admincontas de admin ocultas via REST,web shell emer-run.php,magic-login em mu-pluginsregistro envenenado mais antigo: 23 de junho de 2026Wordfence avisada: 7 de agosto de 2026Sete semanas em que toda checagem do artefato publicado passou, e passou corretamente.
Dois caminhos até um site rodando o plugin. O caminho publicado, aquele que todo controle de cadeia de suprimentos inspeciona, nunca mudou. O caminho de execução buscou um arquivo JSON em um bucket no qual outra pessoa podia escrever, e esse arquivo chegou como JavaScript no navegador do administrador.

O incidente começa com uma credencial que podia escrever

Leia o comunicado do lado do fornecedor e a cadeia de ataque encurta para um passo. Alguém obteve acesso de escrita a um bucket de armazenamento. Todo o resto é consequência.

Comprometimentos de upstream continuam chegando nesse formato. O verme do escopo npm da Red Hat, o repositório que realmente era da Microsoft, os marketplaces de extensões, os registros de contêineres: em cada um deles a carga é a parte que vira reportagem, e a parte que importava era alguém acabar com uma credencial capaz de sobrescrever um artefato que outras pessoas buscam periodicamente.

Onde vive uma credencial dessas? No ambiente de quem desenvolve. Um DIGITALOCEAN_ACCESS_TOKEN num shell, uma configuração do doctl em ~/.config, um login de registro em ~/.docker/config.json, um perfil da AWS, um token do GitHub em ~/.git-credentials, uma chave SSH que pode dar push. Num laptop, num terminal, ao lado do projeto. Em 2026, ao lado do projeto é também onde você roda seu agente de código.

A maioria dos textos deste blog descreve um jeito de virar esse agente contra você. Um relatório de bug que rodou um comando. Uma página web que reescreveu a configuração do agente. Um comentário que ninguém conseguia ver. Uma skill que rodou com a sua identidade. Três bypasses de harness numa única palestra da Black Hat. Tome o catálogo como dado, e assuma que numa terça qualquer um deles cai em cima de você.

O quanto essa terça fica ruim depende de uma pergunta que você pode responder hoje, sem saber qual injeção vai vencer: o que esta máquina tem permissão de escrever, e para quem? Num laptop a resposta é tudo o que você tem permissão de escrever, e se você publica qualquer coisa, isso inclui o artefato que várias centenas de milhares de estranhos buscam sem olhar.

O token não está na máquina

O Bromure Agentic Coding roda o agente em uma VM Linux descartável no Apple Silicon, e cada byte do seu tráfego cruza um proxy no host, fora da caixa em que o agente roda. O Bromure arranja as credenciais em volta dessa fronteira em vez de colocá-las dentro dela.

Coloque seu token de acesso pessoal do DigitalOcean no painel Credentials e o Bromure mantém o valor real no Mac. O que aterrissa na VM é um falso: uma variável de ambiente DIGITALOCEAN_ACCESS_TOKEN e um ~/.config/doctl/config.yaml que fazem o doctl funcionar sem doctl auth init, guardando um valor de espaço reservado. Quando a VM faz uma requisição para api.digitalocean.com, o proxy do host substitui o token real no fio, para aquele destino, e mais nada.

Qualquer coisa rodando dentro daquela VM pode ler o ambiente, dar grep nos dotfiles e percorrer o disco inteiro: o agente, uma dependência, um passo de build, um comando de shell que saiu de uma issue envenenada. O que ela coleta é brm_…. O mesmo arranjo cobre o resto do conjunto: tokens do GitHub, GitLab e Bitbucket, logins de registro de contêiner escritos como um blob base64 falso em ~/.docker/config.json, um kubeconfig sintético com certificados de cliente descartáveis, o Linear, os endpoints HTTPS de banco de dados, e qualquer outra coisa que você adicione em Other API keys. A AWS vai além: o host reassina cada requisição com SigV4 usando o segredo real, de modo que contornar o proxy rende uma InvalidSignatureException em vez de uma escrita não autorizada.

Guardrails decide o verbo, no Mac

Este incidente precisou de uma operação: sobrescrever um objeto num bucket. Guardrails é um motor de políticas dentro do proxy do host, com um modo por recurso: Off, Block destructive ou Read-only. Configure o DigitalOcean como Read-only e toda mutação para api.digitalocean.com volta como um 403 duro que o agente reporta como uma falha comum de API. O Bromure toma essa decisão no macOS. Não há configuração dentro da VM para mudar, nem variável de ambiente para desativar, nem arquivo para editar.

Publicar pergunta antes

Toda entrada de credencial tem Require approval to use. Ligue isso e cada troca falso→real levanta um diálogo de consentimento no host antes que o valor real chegue ao fio, com concessões limitadas no tempo no caminho SSH: cinco minutos, uma hora, o resto da sessão. Dar push num lançamento é algo que você quis fazer, e custa um clique. Uma escrita que você não iniciou aparece como um diálogo que você não esperava, num momento em que você não esperava nenhum.

Registros e git recebem o mesmo tratamento

O Guardrails classifica o tráfego de registro de contêiner por método contra o próprio hostname do registro: GET e HEAD são um pull, PUT e POST são um push, DELETE é destrutivo. GitHub, GitLab e Bitbucket estão cobertos tanto na API REST quanto no git sobre HTTPS, onde git-receive-pack conta como escrita e é bloqueado no modo Read-only enquanto os fetches sempre passam. Um perfil que lê todo o seu grafo de dependências e não dá push em nada são duas configurações.

O ambiente é uma lista que você escreveu

Um perfil do Bromure enxerga as pastas do Mac que você compartilhou com ele, até oito, cada uma montada sob /home/ubuntu. Todo o resto no seu Mac não está montado, não é alcançável e não é enumerável. O conjunto de credenciais é aquele que você adicionou a esse perfil, de modo que um perfil que você usa para construir um frontend não guarda credencial de cluster alguma para nada encontrar, oculta ou não.

No laptop de quem desenvolveDIGITALOCEAN_ACCESS_TOKEN=dop_v1_…~/.config/doctl, ~/.docker/config.json,~/.git-credentials, ~/.aws, ~/.ssho agente é virado, uma vezuma issue, uma página, um README, uma linha de logagora ele lê o que você lêum PUT para um buckete o artefato que seus usuários buscamautomaticamente agora é delesNum perfil do BromureDIGITALOCEAN_ACCESS_TOKEN=brm_…todo dotfile está presente e todosegredo dentro deles é um substitutoo valor real vive no Mactrocado no fio pelo proxy do host,para api.digitalocean.com e nenhum outro lugarGuardrails: Read-only → 403a escrita é julgada no macOS, eRequire approval põe um diálogo antes
A mesma credencial de escrita, em dois lugares. Num laptop ela é um token real num arquivo real, a um agente virado de distância do artefato que seus usuários buscam. Num perfil do Bromure o token real nunca entra na VM, e a operação que ele executaria é julgada pelo host.

A busca em tempo de execução também cruza o proxy

Agora pegue o lado de baixo da história, onde você é um dos 350.000. O que você consegue pegar ali depende de onde você coloca a inspeção.

O painel Supply Chain do Bromure filtra buscas de pacote em npm, PyPI, Cargo, RubyGems, Maven, NuGet, módulos Go e Packagist: uma barreira de idade que vem ligada por padrão e recusa qualquer coisa publicada nos últimos dois dias, consultas ao OSV, socket.dev ou Delpi como provedor de filtragem, remoção de scripts de instalação que reescreve o tarball e conserta o hash dos metadados do registro para que o npm ainda verifique, e um aviso antes que tarballs fixados por lockfile passem sem modificação. O .npmrc e o pip.conf dentro da VM podem apertar essas regras e não podem afrouxá-las.

Essas regras se pagam por causa de onde o Bromure as aplica. O proxy do host as aplica a uma requisição em vez de a um pacote, de modo que um arquivo JSON puxado de um bucket de fornecedor em tempo de execução por um passo de build, uma CLI, um servidor MCP ou a própria aplicação cruza a mesma fronteira que um tarball. Tudo o que a VM diz à rede cruza essa fronteira.

Isso muda o aspecto das sete semanas. Com o Session trace em Activity, o Bromure registra o host, o status, a latência, o relatório de troca e quaisquer avisos de vazamento para cada requisição que a VM faz. Coloque em Everything e ele guarda também os corpos, para todo host, cifrados com a mesma chave de keychain dos segredos do seu perfil e legíveis no Trace Inspector. O dia em que um feed do qual seu build depende começa a responder diferente é um diff que você pode abrir, no Mac, num log ao qual a VM não tem acesso de escrita.

Inspeção dentro do gerenciador de pacotesvê: tarballs, versões, lockfilesroda quando você instala algoGET banner.json → o bucket do fornecedornão é uma instalação, então não é um eventosete semanas, nenhum registro guardadoInspeção no proxy do hostvê: cada requisição que a VM fazidade, OSV, filtragem, remoção de scriptGET banner.json → o bucket do fornecedorhost, status, latência, relatório de trocae o corpo, no nível Everything
Onde a inspeção acontece decide o que ela consegue ver. Um controle no gerenciador de pacotes só inspeciona coisas que foram publicadas; um controle no proxy do host inspeciona cada requisição que a VM faz, incluindo a busca em tempo de execução que carregou esta carga.

Um feed envenenado é também, no fim das contas, texto chegando a uma máquina. Quando ele alcança o agente como um tool_result, um arquivo, uma página buscada ou um arquivo de instruções como CLAUDE.md ou AGENTS.md, o painel Prompt Injection o pontua no dispositivo com um modelo local, e nada sai do Mac. Escolha Ask me what to do e a requisição pausa com o trecho sinalizado à mostra; escolha Block unilaterally e o agente recebe um 451 duro. Quando você quiser a máquina fora do caminho, Erase home reinicia /home/ubuntu e Reset to base reclona o disco de sistema do workspace a partir da imagem base.

A BdThemes vai corrigir o escape, o bucket vai ganhar chaves mais apertadas, e a próxima versão desta história vai chegar com outro fornecedor e outro feed. O que dura é o arranjo por baixo. Seu ambiente de desenvolvimento guarda um punhado de credenciais, cada uma capaz de reescrever algo que milhares de pessoas buscam periodicamente, e esse mesmo ambiente agora roda um agente lendo texto de estranhos o dia inteiro.

A maioria dos textos aqui pergunta o que seu agente deveria poder ler. Responda também à outra pergunta, a que é desconfortável num laptop e fácil num perfil: o que ele tem permissão de escrever, e para quantas pessoas?

Instale o Bromure Agentic Coding, e dê ao agente uma máquina que não guarda a chave de nada que você publica.