3 pontos por GN⁺ 2025-12-19 | 1 comentários | Compartilhar no WhatsApp
  • O Seattle Times evitou por acaso o ataque Shai-Hulud 2.0, mas partiu do princípio de que sorte não pode ser estratégia de segurança e adotou defesas no lado do cliente
  • Melhorias do npm como Trusted publishing / provenance / tokens granulares fortalecem o lado da “publicação”, mas ainda deixam uma lacuna: não impedem a execução de código malicioso no momento de “instalação/atualização”
  • O pnpm usa o mesmo registro do npm, mas adiciona controles que dificultam a execução de pacotes maliciosos na etapa de consumo (install/update)
  • No piloto, aplicaram 3 controles do pnpm para bloquear separadamente vetores como execução de scripts de lifecycle, instalação imediata da release mais recente e rebaixamento de confiança
  • Exceções são tratadas não como falha, mas como parte do design, com o objetivo de operar em defense-in-depth, documentando as exceções enquanto as demais camadas continuam protegendo

Contexto do incidente e premissas

  • Em novembro de 2025, ocorreu um caso em que um worm autorreplicante no npm infectou 796 pacotes e se espalhou em um universo de 132 milhões de downloads mensais
  • O ataque usou scripts preinstall para roubar credenciais, instalar backdoors persistentes e, em alguns ambientes, até apagar o ambiente de desenvolvimento
  • O motivo de nossa organização não ter sido afetada não foi uma defesa forte, mas o acaso de não ter executado npm install/npm update durante o período do ataque
  • Em uma organização de notícias, a confiança é central, e um comprometimento da cadeia de suprimentos pode expor dados de clientes, credenciais de funcionários, infraestrutura de produção e código-fonte, além de gerar altos custos de recuperação e notificação

Equipe e contexto de adoção

  • O Seattle Times usa npm há muito tempo como gerenciador de pacotes padrão, e já houve experimentos com Yarn, mas eles não se consolidaram
  • O motivo para adotar pnpm é a presença de controles de segurança no lado do cliente que complementam as melhorias no nível do registro
  • O pnpm foi considerado uma opção viável de migração por ser um drop-in replacement que usa o mesmo registro, os mesmos comandos e o mesmo fluxo de trabalho
  • Não se trata de um estudo de caso concluído, mas do compartilhamento dos problemas e do raciocínio de uma equipe real que está começando agora a lidar com segurança da cadeia de suprimentos

Por que controles no lado do cliente são necessários

  • As melhorias de segurança do npm realmente tornaram mais difícil a publicação de pacotes maliciosos após o comprometimento de uma conta
  • Essas melhorias protegem o lado da “publicação”, mas não impedem a instalação em si de um pacote malicioso na etapa de “consumo”
  • Em npm install/npm update, lifecycle scripts (preinstall/postinstall etc.) podem executar código arbitrário com privilégios do desenvolvedor antes de qualquer avaliação de segurança do pacote
  • Esses scripts podem acessar credenciais do npm/GitHub/AWS/BD, código-fonte, infraestrutura em nuvem e todo o sistema de arquivos
  • Ataques como o Shai-Hulud exploram exatamente essa estrutura: se a conta do mantenedor for comprometida, scripts maliciosos podem ser executados no momento da instalação, antes que a comunidade consiga detectar o problema
  • As melhorias do npm no lado da publicação e os controles do pnpm no lado do consumo foram combinados como defesas complementares, formando uma estratégia de “defense-in-depth”

As 3 camadas aplicadas

  • No piloto, foram usados em conjunto 3 controles voltados a vetores de ataque diferentes
  • Cada controle tem uma válvula de escape para exceções realistas e foi desenhado partindo da premissa de que, em produção, exceções serão necessárias

Control 1: Gerenciamento de Lifecycle Scripts

  • No pnpm, por padrão, é possível bloquear lifecycle scripts, permitindo que a instalação prossiga com um aviso
  • Como havia receio de que avisos fossem ignorados, foi escolhido strictDepBuilds: true, forçando a instalação a falhar imediatamente se houver scripts
  • Exemplo de configuração no pnpm-workspace.yaml com os campos a seguir
    • strictDepBuilds: true
    • onlyBuiltDependencies: lista de permissões para pacotes com scripts de build necessários
    • ignoredBuiltDependencies: lista de bloqueio (ou ignorados) para pacotes com scripts de build desnecessários
  • “Scripts necessários” foram definidos como ações como compilação de extensões nativas ou linkagem de bibliotecas dependentes de plataforma
  • “Scripts desnecessários” foram definidos como otimizações ou configurações opcionais que, na forma de uso da equipe, não afetam a funcionalidade
  • A falha na instalação força as etapas seguintes
    • o pnpm informa claramente quais pacotes têm scripts
    • investigar e entender o que os scripts fazem
    • decidir conscientemente, com julgamento humano, permitir ou bloquear, e documentar essa decisão
  • A equipe do pnpm está considerando tornar strictDepBuilds: true o padrão na v11, além de revisar os nomes da sintaxe de allow/deny

Control 2: Cooldown de Release

  • Versões publicadas recentemente ficam bloqueadas para instalação por um período de cooldown, dando tempo para a comunidade detectar e remover pacotes maliciosos
  • Exemplo de configuração no pnpm-workspace.yaml com os campos a seguir
    • minimumReleaseAge: <duration-in-minutes>
    • minimumReleaseAgeExclude: lista de exceções para casos como hotfixes urgentes
  • É preciso abandonar o hábito de pensar que “o mais recente é o melhor” e adotar a ideia de que, do ponto de vista da cadeia de suprimentos, algo um pouco mais antigo pode ser mais seguro
  • No ataque de setembro de 2025 (16 pacotes, incluindo debug e chalk), a remoção ocorreu em cerca de 2,5 horas; no Shai-Hulud 2.0, de novembro de 2025, levou cerca de 12 horas
  • Dependendo da tolerância a risco da organização, o cooldown pode ser de horas, dias ou semanas, e qualquer uma dessas formas teria bloqueado esse ataque
  • Isso combina bem com a realidade de organizações que já não conseguem usar sempre a versão mais recente, então o cooldown não atrapalha muito o trabalho
  • Quando for realmente necessário, como em um patch de segurança ou bug crítico, a exceção pode ser liberada após revisão

Control 3: Política de Confiança

  • Se surgir uma versão publicada com autenticação mais fraca do que a versão anterior, a instalação é bloqueada
  • Isso é tratado como sinal de um caso em que a conta do mantenedor foi comprometida e a publicação foi feita da máquina do atacante, e não do CI/CD oficial
  • Exemplo de configuração no pnpm-workspace.yaml com os campos a seguir
    • trustPolicy: no-downgrade
    • trustPolicyExclude: lista de exceções para casos como migração de CI/CD
  • O texto explica que o npm acompanha 3 níveis de confiança na publicação de pacotes, do mais forte para o mais fraco
    • Trusted Publisher: baseado em GitHub Actions + OIDC + npm provenance
    • Provenance: attestation assinada no CI/CD
    • No Trust Evidence: publicação com username/password ou token
  • Se a nova versão tiver nível de confiança inferior ao da anterior, a instalação falha
  • No ataque de agosto de 2025 da s1ngularity, em que o atacante publicou localmente uma versão maliciosa sem acesso ao CI/CD e sem provenance, esse controle teria bloqueado a instalação
  • Como exemplos legítimos de rebaixamento, são citados a entrada de um novo mantenedor, migração de CI/CD e hotfix manual durante indisponibilidade do CI/CD; nesses casos, após investigação, o pacote entra na lista de exceções
  • Esse recurso é novo, adicionado ao pnpm em novembro de 2025, e ainda estão aprendendo com que frequência rebaixamentos legítimos realmente acontecem

Exemplo de funcionamento combinado das camadas: patch de vulnerabilidade no React

  • Em um cenário em que é preciso aplicar imediatamente o patch de uma vulnerabilidade crítica em React Server Components, divulgado em dezembro de 2025
  • Normalmente, o cooldown bloquearia a “instalação de uma versão recém-publicada”, mas, por se tratar de um patch crítico de segurança, não dá para esperar
  • Nesse caso, adiciona-se a versão específica do React em minimumReleaseAgeExclude, mas só depois de revisar o aviso da vulnerabilidade e a legitimidade do patch
  • Mesmo com a exceção aplicada, as outras camadas continuam protegendo
    • O React normalmente não tem lifecycle scripts, então, se um script aparecer na versão do patch, isso vira imediatamente um sinal suspeito e pode ser bloqueado
    • Se um atacante roubar credenciais e publicar localmente um “patch”, ele pode ser bloqueado por rebaixamento do nível de confiança
  • A ideia é tratar a exceção não como “falha de segurança”, mas como um design em que, mesmo quando uma camada é contornada, as outras continuam ativas, eliminando o ponto único de falha

Resultado da aplicação piloto

  • Foi feito um PoC aplicando os 3 controles em um serviço de backend
  • O tempo total de preparação, incluindo investigação, entendimento e definição de acesso, foi de apenas algumas horas
  • O pnpm identificou 3 pacotes com lifecycle scripts
    • esbuild: otimiza o início do CLI em milissegundos, mas a equipe usa apenas a API JavaScript, então foi considerado desnecessário
    • @firebase/util: faz configuração automática do SDK cliente, mas a equipe usa apenas o SDK de servidor, então foi considerado desnecessário
    • protobufjs: faz verificação de compatibilidade de esquema, mas é usado apenas como dependência transitiva e foi considerado desnecessário para o caso de uso da equipe
  • Após consultar a documentação e analisar os scripts (incluindo uso de IA para ajudar a interpretar os scripts), concluíram que os 3 eram desnecessários no contexto da equipe e os bloquearam
  • Não houve impacto funcional
  • O atrito (friction) é uma funcionalidade intencional: um mecanismo que impede confiar implicitamente no código executado no ambiente
  • Quando uma nova dependência tiver scripts, a expectativa é gastar cerca de 15 minutos em revisão e documentação

Lições aprendidas durante a operação

  • A combinação das camadas no lado do cliente com as melhorias do npm no lado da publicação fez com que o defense-in-depth realmente funcionasse na prática
  • Mesmo ao aplicar exceções, ainda restam outras camadas, o que reduz a insegurança em relação a essas exceções
  • A mudança de modelo mental de “conveniência em primeiro lugar” para “segurança em primeiro lugar” leva tempo, mas depois se torna natural
  • Isso é viável na prática até para organizações de porte médio sem equipe de segurança dedicada
  • A trust policy é um recurso com apenas algumas semanas de vida, então ainda é preciso aprender mais sobre a frequência de rebaixamentos legítimos e o operacional do dia a dia
  • Há planos de expandir isso em breve para outras bases de código, o que deve trazer mais dados em aplicações com grafos de dependência diferentes

Dicas de adoção para outras equipes

  • A recomendação é começar por um projeto e usar isso para aprender o fluxo de trabalho e os pontos de atrito
  • Como exceções podem ser necessárias em lifecycle scripts, release cooldown e trust downgrade, projete com exceções em mente desde o início
  • Recomenda-se usar strictDepBuilds: true desde o primeiro dia, forçando falha na instalação em vez de depender de avisos
  • Documente todas as exceções para deixar um rastro de auditoria e facilitar revisões futuras
  • Lembre-se de que a exceção em uma camada ainda preserva a proteção das demais

1 comentários

 
bichi 2025-12-19

pnpm! pnpm! pnpm! Realmente dá para confiar.