Nos filmes de terror, as ameaças mais assustadoras não são aquelas que arrombam a porta da frente — são aquelas que já estão lá dentro, os fantasmas escondidos nas paredes que ninguém percebeu.
As cadeias de suprimentos de software modernas funcionam da mesma maneira.Quando criamos e distribuímos aplicativos hoje em dia, tendemos a confiar em nossa equipe e nas bibliotecas que instalamos deliberadamente.
Bém, por baixo da superfície do seu package.json esconde-se um imenso iceberg invisível de código.Esse é o mundo das dependências transitivas, e é exatamente por aí que muitos ataques modernos à cadeia de suprimentos começam.
A ilusão de controle: suas dependências diretas
Ao executar npm install, você pode estar adicionando cinco ou dez bibliotecas que conhece e nas quais confia. Essas são suas dependências diretas. Você verificou o número de estrelas no GitHub, conferiu a frequência de atualização e está confiante nelas.
No entanto, cada uma dessas cinco bibliotecas pode depender de outras dez. E essas dez, por sua vez, dependem de mais dez. Antes que você perceba, um aplicativo web “simples” pode facilmente chegar a mais de 1.000 pacotes armazenados no seu node_modules . Essas são suas dependências transitivas, os “pacotes dos seus pacotes”, e representam a grande maioria do código que você realmente envia para produção.
Se você quiser verificar quais dependências diretas estão instaladas sem precisar abrir o arquivo package.json, você pode executar npm ls. Esse comando listará no terminal as dependências diretas que você instalou.
Por que as dependências transitivas são o “fantasma” perfeito
O perigo das dependências transitivas é que elas são, em grande parte, invisíveis no fluxo de trabalho do desenvolvedor comum. Raramente verificamos o terceiro ou o quarto nível da nossa árvore de dependências. Os invasores sabem disso e mudaram suas táticas para explorar três “pontos fracos” específicos.
Typo-Squatting:
Registrar um nome de pacote que pareça quase idêntico a um nome popular (por exemplo, lodash vs lodas_h). Basta um pequeno erro de digitação em uma dependência de nível profundo para que um script malicioso passe a ser executado em seu pipeline de compilação.
Digamos que você queira instalar o Express para poder desenvolver uma API ou um serviço de back-end. A memória motora entra em ação e, ao acessar o terminal para instalar o `express`, em vez de digitar `npm install express`, você digita `npm install expres`. A diferença é que você esqueceu o segundo “s” da palavra. É provável que você nem verifique a pasta `node_modules` — simplesmente vai confiar que o pacote foi instalado corretamente. É aí que os problemas podem começar.
Esteja sempre atento aos pacotes que você está instalando. Sempre que possível, dê preferência a pacotes com escopo. Os pacotes com escopo geralmente são obtidos de registros privados ou controlados pela organização, o que adiciona uma camada extra de confiança.
Confusão de dependências
Fazer com que um sistema de compilação baixe uma versão “pública” maliciosa de um pacote, em vez da sua versão interna “privada”. Os pacotes com escopo ajudam a mitigar ataques de confusão de dependências. Um pacote com escopo tem um formato semelhante a este: `@scope/your-awesome-package`.
Quando uma organização utiliza registros privados (internos) e públicos (npm), um invasor publica um pacote público com o mesmo nome de um pacote privado, mas com um número de versão superior. De acordo com a Spectral, “Quase 49% das organizações estão expostas a riscos de um ataque de confusão de dependências.”
A atualização “Amaldiçoada”
Um invasor obtém acesso a uma pequena biblioteca de utilitários legítima, utilizada como dependência por uma importante estrutura de desenvolvimento. Um exemplo de um pequeno utilitário seria o pacote `@ctrl/tinycolor`. Um exemplo recente é o ataque à cadeia de suprimentos Shai-Hulid, no qual uma biblioteca de utilitários comprometida foi usada para distribuir código malicioso. Eles lançam um “patch” que inclui um backdoor. De repente, milhares de aplicativos são “possuídos” da noite para o dia, sem que nenhum desenvolvedor tenha alterado seu próprio `package.json`.
Um backdoor (originalmente chamado de trapdoor) é um método oculto de contornar a autenticação normal e obter acesso privilegiado a um sistema. O Malwarebytes tem um ótimo artigo sobre backdoors, e esta citação resume muito bem o assunto.
“Um backdoor é qualquer método pelo qual usuários autorizados e não autorizados conseguem contornar as medidas normais de segurança e obter acesso de alto nível (também conhecido como acesso root) a um sistema de computador, rede ou aplicativo de software.”
História das portas dos fundos
As backdoors, ou trapdoors, surgiram nas décadas de 1960 e 1970. A criação original da backdoor tinha como objetivo permitir que desenvolvedores de software legítimos tivessem acesso para manutenção, solução de problemas e contornar a autenticação durante o desenvolvimento e as correções do sistema.
As backdoors começaram a evoluir na década de 1980. Passaram a ser utilizadas para atividades maliciosas. Um dos incidentes mais famosos da década de 1980 foi o Worm de Morris, de 1988, que infectou sistemas em toda a internet naquela época. Robert Morris criou o Worm de Morris em 1988. O worm infectou vários sistemas na internet, incluindo a NASA, Harvard e Stanford. Morris violou a Lei de Abuso e Fraude Informática de 1986, mas, embora tenha sido condenado, não cumpriu pena de prisão.
A década de 1990 trouxe consigo um uso mais intenso de backdoors por parte do governo. O Clipper Chip foi uma tentativa fracassada de interceptar comunicações. Apenas um telefone chegou a ser lançado e, em 1996, o projeto foi abandonado.
Desde o final da década de 2000 até a década de 2020, os hackers vêm utilizando backdoors para realizar ataques em grande escala na internet. Eles até começaram a se infiltrar em dispositivos na nuvem e de IoT.
Impacto dos ataques por backdoor
As backdoors são perigosas porque ficam ocultas. A cultura popular há muito tempo faz referência às backdoors — o filme *WarGames*, de 1983, chega a apresentar uma cena que explica como funcionam os mecanismos de acesso ocultos. Às vezes, elas podem estar tão bem camufladas que pode levar meses ou anos para detectá-las.
Além do npm audit
Embora ferramentas como o npm audit são um bom primeiro passo, muitas vezes elas detectam apenas os “fantasmas” que já causaram problemas. Para proteger de verdade sua cadeia de suprimentos, precisamos deixar de lado o uma abordagem reativa para proativo .
Integridade do arquivo de bloqueio
Trate seu arquivo package-lock.json ou yarn.lock como um documento protegido. Nunca ignore alterações nele e certifique-se de que seu pipeline de CI/CD esteja aplicando rigorosamente essas versões. Uma coisa a se ter em mente é sempre fazer o commit dos seus arquivos de lock. Ao fazer o commit do seu arquivo de lock, você está “bloqueando” as versões dos pacotes que foram instalados, para que todos na equipe estejam usando exatamente as mesmas.
No entanto, a integridade vai além da simples correspondência entre números de versão. O arquivo de bloqueio contém um URL e uma hash de integridade (geralmente SHA-512). Esse hash é sua principal defesa contra “envenenamento do Registro”. Se um registro for comprometido e um invasor substituir um pacote legítimo por um malicioso da mesma versão, o hash será alterado. Ao usar npm clean-install (ou npm ci) em seus pipelines de compilação, o instalador falhará se o hash do pacote baixado não corresponder ao arquivo de bloqueio. Isso equivale a uma “impressão digital” do seu código; se a impressão digital não corresponder, você saberá que o invasor adulterou o arquivo.
Além disso, você deve ficar atento aos ataques do tipo “Lockfile Injection”. Nesse tipo de ataque, um colaborador mal-intencionado envia um Pull Request que parece ser uma simples atualização de dependências, mas inclui um lockfile modificado que aponta para uma URL maliciosa de uma dependência transitiva. Sempre faça a revisão por pares das alterações no lockfile com o mesmo rigor com que revisa o código-fonte. Se uma solicitação de pull (PR) adicionar 5.000 linhas a um arquivo de bloqueio para um “patch menor”, é hora de começar a fazer perguntas.
SBOMs (Lista de Componentes de Software)
Se você não sabe o que há na sua “galáxia de dependências”, não poderá protegê-la. A geração de uma SBOM oferece um inventário claro de todos os componentes da sua cadeia de suprimentos de software. Pense na SBOM como a “lista de ingredientes” na embalagem de uma pizza congelada. Se você descobrir mais tarde que o “Corante Vermelho nº 40” é tóxico, poderá verificar sua despensa e saber imediatamente quais itens descartar. No mundo do software, quando um novo CVE (Common Vulnerabilities and Exposures) é anunciado para uma dependência transitiva profunda, um SBOM permite que você pesquise todo o seu ecossistema em segundos, em vez de auditar manualmente cada projeto.
Existem dois padrões principais do setor para SBOMs: SPDX (Software Package Data Exchange) e CycloneDX. Enquanto o SPDX é um padrão ISO frequentemente preferido para licenciamento e conformidade, o CycloneDX é um formato leve que prioriza a segurança, projetado especificamente para a segurança de aplicativos e a análise da cadeia de suprimentos.
Uma postura de segurança verdadeiramente madura não se limita a gerar uma SBOM; ela a utiliza em conjunto com o VEX (Vulnerability Exploitability eXchange). O VEX é um documento complementar que indica se uma vulnerabilidade encontrada em seu SBOM é de fato “acessível” ou “explorável”. Isso evita a “fadiga de vulnerabilidades”, aquela sensação avassaladora de ver 400 alertas “Altos” e não saber qual corrigir primeiro. Ao combinar um SBOM com o VEX, você pode provar aos seus usuários que, embora possa haver um “fantasma” em uma biblioteca que você usa, ele está trancado em uma sala onde não pode fazer mal a ninguém.
Minimalismo
O código mais seguro é aquele que você nunca instala. Antes de adicionar uma nova dependência, pergunte-se: “Será que eu mesmo poderia escrever esse utilitário?” ou “Será que essa biblioteca traz 500 dependências transitivas só para formatar uma data?”
Conclusão: Acabe com a assombração antes mesmo que ela comece
A maioria dos ataques à cadeia de suprimentos que vemos hoje não vem pela porta da frente, mas sim das dependências transitivas que aceitamos sem pensar duas vezes.
Como desenvolvedores, somos os guardiões. Precisamos ser igualmente cuidadosos com o código que utilizamos quanto em relação ao código que escrevemos. É hora de lançar luz nos cantos escuros do node_modules e garantir que não haja fantasmas se escondendo na máquina.

