Segurança Digital

Supply chain attacks em dependências open-source

Auditando o que você não escreveu

17 de jul. de 2025·Ricardo Coelho

O código que roda em produção é majoritariamente escrito por desconhecidos. Um projeto Node típico carrega entre oitocentas e duas mil dependências transitivas pra responder a um único request, e um serviço Python em produção tem mais linhas em site-packages do que em todo o repositório da empresa. A superfície de ataque deixou de ser o código interno há muito tempo, e o vetor mais explorado hoje passa pelo comportamento malicioso no que você importou sem ler. A assimetria entre o rigor aplicado ao código interno e o aplicado à cadeia de suprimentos define o risco atual.

A evolução quantitativa é consistente, mesmo com fontes divergentes em metodologia. A Sonatype, no seu State of the Software Supply Chain, relata mais de 778 mil pacotes maliciosos acumulados em repositórios públicos, com crescimento próximo de 156% ano a ano. Só em 2024, a Snyk identificou cerca de três mil novos pacotes maliciosos publicados, segundo seu relatório anual, salto de ordem de grandeza em relação a poucas dezenas por ano em 2018. Relatórios de ReversingLabs e OpenSSF convergem em descrever escalada mês a mês ao longo de 2024, com incidentes praticamente semanais no npm. O ecossistema concentra a maior parte desses pacotes pela maior superfície, menor atrito de publicação e maior volume de dependências transitivas por instalação.

O caso XZ Utils, tornado público em março de 2024 como CVE-2024-3094, mudou a leitura do problema. O vetor operou por aquisição de confiança: um ator pseudônimo contribuiu por cerca de dois anos, assumiu manutenção e injetou um backdoor no build que habilitaria execução remota via sshd nas versões 5.6.0 e 5.6.1. O mecanismo exposto é o que importa pra auditoria. Um projeto crítico mantido por uma única pessoa absorveu agente malicioso porque a própria escassez de mantenedores criou a pressão social necessária pra aceitar ajuda externa sem verificação adequada.

A ISO 27001:2022 endereçou essa superfície de forma mais explícita. O A.5.21 trata de gestão de segurança na cadeia de TIC, exigindo que requisitos sejam propagados também a sub-processadores, o que em software livre significa explicitar política sobre dependências transitivas. O A.8.28, novo na revisão de 2022, trata de codificação segura e exige processo pra verificar bibliotecas de terceiros antes do uso e mantê-las atualizadas. Lidos com o A.8.30 e o A.5.23, esses controles descrevem um sistema em que toda dependência de terceiros é fornecedor tratado com o mesmo rigor contratual aplicado a fornecedores comerciais, ainda que o contrato seja uma licença MIT e o fornecedor seja pessoa anônima no GitHub.

A implementação operacional desses controles começa pelo inventário, materializado em SBOM gerado no pipeline de build, no formato SPDX ou CycloneDX, armazenado como artefato versionado. Sem SBOM, a pergunta "estamos usando xz 5.6.0?" leva horas de grep nos ambientes de engenharia e dias nos operacionais, e o custo de resposta a incidente cresce de forma não linear com o tamanho do parque. Sobre o inventário se apoia a varredura contínua, integrando ferramentas como Dependabot, Renovate, Snyk, Trivy ou osv-scanner ao pipeline, com política clara sobre severidade e janela de correção. Varredura sem política é ruído, política sem varredura é ficção. Fecha o arranjo o controle sobre o que entra, por meio de lockfiles auditados, verificação de integridade por hash, proxies de repositório privado que permitam quarentena de pacotes novos, e política explícita sobre pinning versus atualização automática.

O trade-off entre pinning agressivo e atualização automática é o ponto onde a maioria dos times trava. Fixar versões reduz a superfície de pacotes maliciosos recém-publicados, porque novas versões precisam ser avaliadas antes de entrar, mas amplia a janela de exposição a CVEs conhecidas, porque atualizações de segurança ficam represadas em backlog. Atualização automática faz o oposto: reduz o tempo entre disclosure e correção, ao custo de transformar cada publicação maliciosa em execução imediata no parque. Nenhuma escolha é neutra, e a escolha racional depende do tipo de componente. Dependências de runtime em serviços expostos exigem pinning com revisão humana, enquanto dependências de build e ferramental toleram janelas maiores. Pacotes de infraestrutura crítica exigem verificação de assinatura e quarentena mínima antes de promoção pra produção. Pra mim, a política uniforme é a falha que repetidamente aparece nas organizações que audito.

O elemento que costuma estar ausente do programa é a observação do comportamento de mantenedores e do padrão de atividade dos pacotes. Pacotes com mudança súbita de mantenedor, reescritas substanciais em releases menores, ou picos de atividade em repositórios dormentes são sinais de alerta identificados em praticamente todos os incidentes recentes. Ferramentas como Socket e Phylum operam nessa camada, e a integração delas com o pipeline é barata comparada ao custo potencial de um incidente na cadeia. A métrica relevante deixou de ser "este pacote tem CVE conhecido" e passou a ser "este pacote exibe comportamento consistente com sua reputação histórica".

A conclusão operacional é desconfortável: auditar o que você não escreveu exige o mesmo rigor de processo que auditar o que você escreveu, aplicado a superfície uma ordem de grandeza maior e com controle parcial sobre os atores. Confiar na popularidade do pacote como proxy de segurança já foi refutado pelos incidentes de 2024. Popularidade aumenta o valor do alvo pro atacante, não a garantia de integridade. Na minha avaliação, o programa de segurança que leva cadeia de suprimentos a sério trata cada dependência como fornecedor e cada atualização como mudança, reconhecendo cada mantenedor desconhecido como risco que precisa ser medido, não assumido.