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

O build publicou a chave

O relatório de incidente ampliado da Beacon CRM atribui o roubo de todo o seu banco de dados de clientes, que cobre mais de 1.500 entidades beneficentes do Reino Unido, a uma chave de acesso da AWS que o próprio processo de build embutiu em um arquivo JavaScript público. Ninguém invadiu a máquina de um desenvolvedor. Uma ferramenta de build copiou uma variável de ambiente para um artefato, que é o que ferramentas de build fazem. A resposta do Bromure Agentic Coding é que a variável não guarda nada que valha a pena copiar.

Não há atacante na primeira metade desta história. Um processo de build copiou um segredo de uma variável de ambiente para um arquivo JavaScript, e um servidor web entregou esse arquivo a quem o pedisse. O roubo, quando veio, foi uma requisição GET.

A Beacon é um CRM usado por entidades beneficentes do Reino Unido para acompanhar doadores, apoiadores e voluntários. Em 12 de agosto, seu CTO, David Simpson, publicou um relatório de incidente ampliado sobre uma violação que a empresa havia divulgado pela primeira vez em 4 de agosto. O The Register noticiou no dia seguinte, a SecurityWeek veio em 14 de agosto, e a causa raiz cabe em uma linha:

Uma chave de acesso da AWS potencialmente exposta em artefatos de build JavaScript públicos.

Às 01:20:16 UTC de 27 de julho, alguém começou a usar essa chave e manteve o acesso por uma hora e vinte e sete minutos. A avaliação da Beacon sobre o que saiu:

Uma cópia do banco de dados que guarda todos os dados de clientes da Beacon, incluindo arquivos anexos, foi feita e provavelmente baixada em formato legível pelo agente de ameaça.

Isso cobre mais de 1.500 entidades: nomes de apoiadores, telefones, endereços de e-mail, endereços postais, registros de doação e arquivos anexos. Nenhum dado de cartão ou bancário, já que os clientes da Beacon não os guardam ali. O ICO analisou ao menos uma entidade vítima e concluiu que ela não tem responsabilidade alguma pela violação, o que é correto e também um consolo frio para uma equipe de captação de recursos que precisa explicar aos seus apoiadores para onde foram os endereços residenciais deles.

A Beacon criptografava os dados em repouso. Isso não mudou nada. A Cybersecurity News faz o ponto de que a AWS descriptografa em nome de quem quer que detenha credenciais válidas. Criptografia em repouso protege você contra alguém que saia carregando um disco, e não tem opinião alguma sobre um chamador com a chave.

A parte que vale encarar

Leia o relatório de incidente procurando a intrusão e você não vai encontrá-la. Não houve e-mail de phishing, nem mantenedor comprometido, nem dependência envenenada ou injeção de prompt, nem malware no notebook de um engenheiro. Nada nesta história contornou um controle, porque ninguém extraiu a credencial de lugar nenhum. O processo de build da Beacon copiou um valor de uma variável de ambiente para um bundle, a Beacon implantou o bundle como um recurso estático, e um servidor web o entregou a todo cliente que o requisitou, conforme projetado. O canal de exfiltração foi uma tag <script>.

O mecanismo não é exótico. Builds de front-end embutem variáveis de ambiente de propósito, porque o navegador não tem ambiente algum para ler em tempo de execução:

  • O Vite substitui qualquer variável com o prefixo VITE_ em import.meta.env no momento do build. O Next.js faz o mesmo para NEXT_PUBLIC_, e o Create React App usava REACT_APP_. O prefixo é como você pede a substituição.
  • O DefinePlugin do webpack e o --define do esbuild trocam um token no seu código por uma string. Eles não têm noção de quais strings são secretas, nem meio de adquirir uma.
  • Um source map é uma segunda cópia. Frameworks com renderização no servidor acrescentam um terceiro caminho: um valor lido em código que acaba em um componente de cliente é serializado no payload.

Cada uma dessas cópias sai do ambiente de quem, ou do que, iniciou o build. O ambiente é a matéria-prima, e o build é uma máquina de copiá-lo para um arquivo que você depois publica.

O trajeto de um segredo até a web públicaambiente de buildAWS_SECRET_…=realo bundler embutesubstituição textodeployapp.[hash].jsservido ao público200 OK, em cachequalquer um lêview-sourceNada acima é uma exploração. Abaixo está a única etapa que envolve um atacante.credenciais válidas, chamadas válidas27 jul., 01:20:16 UTC · 1 h 27 minbanco inteiro + anexos1.500+ entidades, formato legívelcriptografia em repouso: sem efeitoa AWS descriptografa p/ chamador válidoCausa raiz, horários e escopo do relatório de incidente da Beacon de 2026-08-12, via The Register e SecurityWeek.
O trajeto da credencial até a web pública na Beacon. Nada nesse caminho é uma vulnerabilidade, uma intrusão ou um controle contornado. Cada passo é uma ferramenta fazendo aquilo para o que existe.

A estação de trabalho em que isso começa

A violação da Beacon é uma história de nuvem em suas consequências. Sua causa mora numa estação de trabalho de desenvolvedor, e a estação de trabalho está mudando.

Um agente trabalhando em um ticket faz tudo isso numa tarde comum. Ele adiciona uma variável de ambiente para que um recurso possa alcançar um serviço. Ele edita vite.config.ts ou next.config.js. Ele escreve a linha que lê a variável, e escolhe o módulo em que essa linha vive, que é a decisão que define se o valor fica no servidor ou cruza para o bundle. Ele roda npm run build. Ele comita dist/ quando um alvo de deploy quer o artefato versionado.

Nada disso exige que o agente cometa um erro, e você não recebe sinal algum se ele cometer. O build passa, já que uma string é uma string. O bundle sai como uma única linha minificada, então nada no diff chama sua atenção. O valor funciona, então o recurso funciona e o ticket fecha.

Enquanto isso, o espaço de trabalho em que um agente roda está abastecido de credenciais, porque credenciais são o que o torna útil: chaves de nuvem para que ele possa checar um bucket, um token do GitHub para que possa enviar um branch, credenciais de registro e de banco de dados, uma chave de API de modelo. Cada uma delas é uma string parada num ambiente que um processo de build lê por inteiro.

O relatório da Beacon faz uma pergunta direta a esse arranjo. Algo no seu espaço de trabalho copia o seu ambiente para um arquivo, mais cedo ou mais tarde. Pergunte-se o que ele leva.

Num espaço de trabalho Bromure, ele leva uma string de região

O Bromure Agentic Coding roda o agente numa VM Ubuntu descartável sobre o framework Virtualization da Apple, com um proxy MITM do lado do host como única rota até a rede. O design de credenciais decorre disso: os segredos reais ficam no seu Mac, e a VM recebe valores que parecem certos e não valem nada.

A AWS ganha tratamento próprio, porque o SigV4 nunca envia o segredo. O SDK consome o segredo no cliente para calcular um HMAC e coloca a assinatura no fio, de modo que o Bromure não pode trocar um falso por um valor real em trânsito como faz com um token bearer. Em vez disso, ele move a assinatura.

Comece pelo que um processo de build encontraria. O Bromure exporta AWS_DEFAULT_REGION e AWS_REGION para dentro da VM e nenhuma chave, segredo ou token de sessão. O raciocínio está no código-fonte, em SessionDisk.swift:

NÃO podemos exportar AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN aqui: variáveis de ambiente vencem credential_process na cadeia do SDK, e também derrubariam a garantia de nenhum segredo em disco (variáveis de ambiente vazam via /proc, ps -E e histórico do shell).

Essa lista de canais de vazamento descreve inspeção de processo. Um bundler lendo process.env pertence a ela, e a mesma decisão o cobre. Nenhum AWS_SECRET_ACCESS_KEY existe no ambiente para um build embutir, então o DefinePlugin não substitui nada, import.meta.env não carrega nada, e NEXT_PUBLIC_AWS_SECRET_ACCESS_KEY, o erro da Beacon cometido de propósito, publica uma string vazia.

O SDK ainda funciona. ~/.aws/config aponta para um auxiliar credential_process:

[default]
credential_process = /mnt/bromure-meta/bromure-aws-creds.py
region = eu-west-2

O auxiliar lê um documento JSON de um socket. Ele devolve o AccessKeyId real, que identifica você em vez de autenticá-lo, pareado com uma SecretAccessKey que o Bromure gera para a sessão: quarenta caracteres tirados do alfabeto que um segredo real da AWS usa, de modo que boto3, a CLI aws e o Terraform os aceitam e assinam como de costume. A assinatura que eles produzem está fadada a falhar.

Então o host conserta. O AWSResigner reconhece qualquer requisição *.amazonaws.com que passe pelo proxy, retira o cabeçalho Authorization do convidado e recalcula o SigV4 com credenciais que existem apenas no espaço de endereçamento do processo do host, acrescentando o X-Amz-Security-Token real quando o perfil carrega material STS. Seu terraform apply funciona, e o segredo nunca entrou na máquina que o executou.

Contorne o proxy e a AWS responde:

An error occurred (InvalidSignatureException) when calling the
ListBuckets operation: The request signature we calculated does not
match the signature you provided.

A credencial falha fechada. Uma que só funciona quando roteada pelo seu Mac não pode ser exercida do notebook de um estranho, que é a propriedade que faltava à chave publicada da Beacon.

CONVENCIONAL: o segredo está na sala junto com o buildmáquina do desenvolvedorAWS_SECRET_ACCESS_KEY = realassina SigV4 aqui · bundler lê aquia AWS aceitade qualquer lugarqualquer cópia do ambienteé uma credencial que funcionaBROMURE: o segredo nunca está nessa salaVM: agente, build, bundlersem chave, segredo ou tokenassina com falso de 40 charsproxy do host: AWSResignerremove Authorizationreassina com o segredo reala AWS aceitasó via seu Macfora do proxy:InvalidSignatureException
O Bromure calcula a assinatura em outro lugar. Mandar um segredo para dentro da máquina que executa código não confiável é o que torna uma variável de ambiente digna de roubo; assinar fora dessa máquina remove o motivo de ir olhar.

O resto do painel é chamariz

A AWS é o caso especial. Para o resto, a fronteira do fio faz o trabalho: cada credencial que você configura aparece dentro da VM como um falso que preserva a estrutura, derivado do valor real mais um sal de 32 bytes por instalação, através de HKDF-SHA256. O valor real fica criptografado no seu Mac, e o proxy o substitui na requisição depois que os bytes já saíram da VM, e somente quando a requisição tem como destino o host para o qual aquela credencial foi cunhada.

Isso resolve toda a classe de “uma ferramenta copiou o ambiente para algum lugar aonde ele não deveria ter ido” da mesma maneira, seja qual for a ferramenta:

Chaves de API de modelos e fornecedores

ANTHROPIC_API_KEY é sk-ant-api03-brm-…. OPENAI_API_KEY é sk-brm-…, XAI_API_KEY é xai-brm-…. Preservam a estrutura, então as CLIs as aceitam sem reclamar, e são inertes em qualquer outro lugar.

Tokens de Git, registro e nuvem

GH_TOKEN é ghp_ mais 36 caracteres, o GitLab é glpat- mais 20, a DigitalOcean é dop_v1_ mais hexadecimal. ~/.git-credentials, ~/.docker/config.json, ~/.kube/config e ~/.config/doctl/config.yaml estão todos presentes, todos preenchidos, todos falsos.

Um prefixo NEXT_PUBLIC_

Prefixe qualquer um deles e o bundler faz o que você mandou. O artefato publicado carrega enchimento em forma de brm-docker-… ou de ghp_, e o erro te custa um redeploy em vez de um aviso de violação.

Um dist/ ou source map comitado

Mesma resposta, e ela não depende de ninguém perceber. O artefato pode ficar num repositório público, ser indexado e raspado por todo crawler de segredos em atividade, e a string que eles colhem não autentica coisa alguma.

O Bromure deriva os falsos do mesmo jeito toda vez, então uma ferramenta que gera a impressão digital da própria chave, como o Claude Code faz ao guardar em cache um hash de chave, nunca vê a credencial mudar entre sessões. Também não há interruptor a esquecer. O proxy é a única rota de saída da VM, então uma requisição que o contorna carrega um substituto e falha lá na frente.

O chamariz publicado é um fio de armadilha

A chave da Beacon ficou num arquivo público por um período desconhecido antes de 27 de julho, e o primeiro sinal que alguém teve foi um pico nos relatórios AWS Cost and Usage de 27 e 28 de julho, lidos depois do fato.

O detector de comprometimento do Bromure vigia as suas credenciais em vez da reputação do destino, que é o que lhe permite disparar contra um host que ninguém jamais nomeou. Um autômato de Aho-Corasick construído a partir dos próprios falsos cunhados pelo espaço de trabalho varre cada requisição de saída, cabeçalhos e corpo. Um falso com destino a um host fora do escopo para o qual foi cunhado conta como tentativa de exfiltração, e o Bromure responde:

  1. O proxy recusa a requisição com HTTP 451, e nem um byte chega ao destino.
  2. O Bromure pausa a VM na hora.
  3. O Bromure levanta um alerta nomeando a credencial, o host para o qual ela foi cunhada e o host para o qual foi observada indo.

O Bromure então marca o espaço de trabalho como comprometido, de modo que o lançamento seguinte exige apagar a imagem de disco da VM e o home persistente. Seus tokens, chaves SSH e configurações sobrevivem a isso.

Aponte o mecanismo para o cenário da Beacon. Um falso cunhado para um destino aparecendo numa requisição para outro é a assinatura, e não importa como o falso saiu: um bundle vazado, um dotfile roubado, um estranho curioso com um navegador. A primeira pessoa que tentar a credencial que encontrou se anuncia sozinha, num log que é seu, no momento em que a tenta.

O registro que a Beacon diz que nunca terá

Simpson encerra o relatório de incidente sobre os limites do que a Beacon consegue estabelecer:

Há coisas que talvez nunca consigamos descobrir sobre este incidente.

A Beacon enuncia o limite sem rodeios. Os objetos específicos, o destino exato dos downloads e a atribuição definitiva de quais objetos foram acessados não podem ser determinados a partir dos logs disponíveis. A conclusão da Beacon de que o banco inteiro saiu é uma inferência tirada do formato de um relatório de cobrança: um volume de transferência nos dados de Cost and Usage que bate com o tamanho aproximado do que a Beacon armazena. É bom trabalho forense sem nada com que trabalhar, e cada entidade que agora escreve aos seus apoiadores está se apoiando nisso.

Um espaço de trabalho Bromure produz esse registro como subproduto do seu modo de funcionar. Cada requisição cruza o proxy do host, então o proxy anota o que aconteceu:

$ bromure-cli trace ls
HOST                          METHOD  STATUS  MS    FLAGS
api.anthropic.com             POST    200     412   swap×1
s3.eu-west-2.amazonaws.com    GET     200     88
registry.npmjs.org            GET     200     31
api.github.com                POST    201     140   swap×1

trace hostnames lista cada host distinto que uma sessão contatou, trace summary agrega o conjunto, e trace leaks mostra as credenciais não gerenciadas. O Trace Inspector (⇧⌘I) dá a mesma visão com os corpos das requisições, e o Log de Segurança (Window → Supply Chain Log…) acompanha as decisões de cadeia de suprimentos e os 451 conforme acontecem. O Bromure mantém tudo isso criptografado em repouso no seu Mac sob a chave mestra do cofre.

As perguntas que a Beacon só consegue responder por inferência são uma consulta que você mesmo roda: com o que este espaço de trabalho conversou, quando, carregando o quê, e se algo em forma de credencial saiu. Contra os seus próprios dados, no minuto em que você primeiro se pergunta.

O arranjo que fecha a brecha

Os engenheiros da Beacon não fizeram nada fora do comum. Colocar uma credencial numa variável de ambiente é a prática recomendada, e ler variáveis de ambiente durante um build é o que builds fazem. Um banco de dados inteiro de clientes passou pela brecha entre essas duas coisas razoáveis, e nenhuma dose de cuidado a fecha, porque cuidado é uma esperança sobre a atenção, não um controle.

O controle é arranjar as coisas de modo que a cópia não valha nada. Mantenha a credencial real no host, entregue ao espaço de trabalho um substituto que satisfaça toda ferramenta que o leia, assine e substitua fora da máquina que roda o código, e guarde o seu próprio registro de cada requisição que sai. Aí uma ferramenta de build fazendo seu trabalho, um agente fazendo uma edição de aparência razoável e um estranho lendo o seu bundle com calma chegam todos ao mesmo lugar: uma string que não significa nada fora do seu Mac.


Fontes: The Register, “AWS key exposed in JavaScript may have lit way to Beacon's charity data” (13 de agosto de 2026) · SecurityWeek, “Over 1,000 Charities Hit by Beacon CRM Data Breach” (14 de agosto de 2026) · Infosecurity Magazine, “Exposed AWS Access Key Linked to Data Breach Affecting 1500+ UK Charities” · Cybersecurity News, “Beacon CRM Confirms Full Database Theft After AWS Access Key Breach”