No desenvolvimento moderno de software, passamos de um modelo de instalação estritamente baseado na cadeia de suprimentos para um modelo híbrido. Agora, as dependências podem ser obtidas por meio de duas vias: pacotes privados e públicos. Este artigo abordará a confusão de dependências e como ela se relaciona com ataques de dependência transitiva profunda.
O modelo híbrido de cadeia de suprimentos
Today, we have two different ways of installing packages via various tools like npm, pnpm, etc. The concept of a single way to install packages is a thing of the past. Years ago, we would just open our terminal up and type npm install <package>. It would get pulled from the npm registry, and that would be that. Private packages, or scoped packages, can be delivered from the npm registry and are secure by default. You can also host scoped packages in internal registries like Nexus, Artifactory, and Verdaccio.
Um pacote com escopo tem a seguinte aparência: @myorg/mypackage. Ele começa com o símbolo “@”, seguido pelo nome da sua organização, e termina com uma barra “/” e o nome do pacote. Trata-se de um pacote privado e protegido, hospedado no registro do npm. Isso também permite que você agrupe pacotes sob a sua organização. Você ainda pode instalar pacotes públicos que sejam de código aberto. É quase certo que você instalará um pacote público para desenvolver funcionalidades. Portanto, você terá uma combinação de pacotes públicos e privados.
A vulnerabilidade vai se infiltrando quando se utilizam pacotes públicos de código aberto ou quando se tem uma configuração inadequada de CI/CD. Os gerenciadores de pacotes foram criados para oferecer conveniência. Essa conveniência tem um custo. Esses gerenciadores de pacotes priorizam números de versão mais altos em detrimento da origem do código-fonte. Aí, a situação começa a se agravar cada vez mais.
O que é um ataque de confusão de dependências?
Os ataques de confusão de dependências têm como alvo a maneira como os gerenciadores de pacotes modernos resolvem dependências em várias fontes. Em ambientes onde são utilizados registros privados e públicos, esse processo de resolução pode acarretar riscos inesperados.
O que parece ser uma instalação rotineira de dependências pode, na verdade, se tornar um ponto de entrada para código malicioso — especialmente quando os pacotes internos não estão devidamente isolados ou delimitados.
Como funciona a confusão de dependências
Um ataque de confusão de dependências (ou confusão de namespace) explora uma falha na forma como os gerenciadores de pacotes resolvem dependências em vários registros. Ele ocorre quando um sistema de compilação está configurado para baixar pacotes tanto de um registro privado (interno) quanto de um registro público (como npmjs.com).
Se um invasor identificar o nome de um pacote privado sem escopo e publicar um pacote malicioso com exatamente o mesmo nome no registro público, utilizando um número de versão superior, o gerenciador de pacotes identificará a versão pública maliciosa como a “melhor correspondência”, inserindo-a silenciosamente na cadeia de suprimentos. Isso ocorre porque os gerenciadores de pacotes priorizam as versões mais recentes entre nomes correspondentes.
Na prática, os gerenciadores de pacotes priorizam a versão mais alta que atenda à intervalo semver , conforme mencionado acima. Se o seu package.json solicitar ^1.2.0 da internal-tool, e o invasor publica a v99.0.0 no npmjs.com, o gerente presume que a versão pública é uma atualização legítima e a baixa.
Outra situação comum ocorre quando uma organização não utiliza pacotes com escopo. O pacote foi publicado no registro privado como auth-package, mas também há um pacote com o mesmo nome no registro público. O registro buscará aquele com o número de versão mais alto, a menos que seja especificado no arquivo .npmrc .
Análise das dependências transitivas
Uma dependência transitiva é um pacote que não foi instalado intencionalmente por você, o desenvolvedor, mas que é instalado automaticamente quando você instala uma dependência direta para um projeto. As dependências transitivas podem estar ocultas em vários níveis da árvore de dependências, dificultando a identificação da origem das falhas de segurança.
“A maioria dos incidentes de segurança cibernética estava relacionada a patches não aplicados. 60,4% dos incidentes ocorreram quando havia um patch disponível, mas ele não havia sido aplicado.”
Manter-se à frente das tendências e aplicar as correções na data prevista é fundamental para combater falhas de segurança decorrentes de dependências transitivas na cadeia de suprimentos de software. Veja abaixo um exemplo de como se apresenta uma dependência transitiva.
rxjs é uma dependência direta do Angular v20, mas se você fosse dar uma olhada dentro do diretório pasta node_modules e localizasse o rxjs , você verá, dentro do arquivo package.json o tslib . Esse pacote não foi instalado por você, mas veio junto com o rxjs , dentro do projeto Angular v20.
Anatomia de um ataque de confusão de dependências
Vamos considerar um componente básico da interface do usuário que você deseja instalar em um projeto: um botão simples. Esse botão vem diretamente do sistema de design da sua organização, em uma biblioteca de componentes.
Não tem muito segredo. Fundo, fonte com texto e cantos arredondados. Esse botão é exatamente o que está definido no sistema de design. O código foi desenvolvido com React e fica assim:
export const DemoButton = () => { return ( <button style={{ backgroundColor: 'blue', color: 'white' }}> Secure Scoped Action </button> ); };
Instalamos via npm, importamos para o nosso projeto e pronto. Um botão funcional na nossa interface de usuário. Por outro lado, porém, há outro botão sofisticado por aí, e esse botão ou não está restrito ao registro privado, ou está restrito, mas um desenvolvedor instala a versão pública. A versão pública é a maliciosa, que aciona alertas na página inicial, intercepta credenciais em segundo plano ou inverte as cores do site, seja imediatamente ou após um determinado tempo.
O código desse botão é este aqui, que talvez você ainda não tenha verificado:
import React, { useEffect } from 'react'; export const DemoButton = () => { useEffect(() => { document.body.style.filter = 'invert(100%)'; alert("Hacked via Unscoped Dependency!"); }, []); return <button>Click for Update</button>; };
É provável que esse botão não venha como uma dependência direta. No código de exemplo, estou mostrando-o como uma dependência direta porque não tenho um sistema de design completo, com vários componentes. Se você instalar uma biblioteca de interface do usuário não verificada ou um pacote que sirva como dependência para o seu sistema de design, um botão malicioso poderia ser introduzido de forma transitiva.
Sejam introduzidas diretamente ou de forma transitiva, essas dependências passam a fazer parte do limite de confiança do seu aplicativo — e se tornam uma superfície de ataque em potencial, caso não sejam controladas.
Ataques pré e pós-instalação
Os invasores utilizam pré e pós-instalação para executar cargas maliciosas antes mesmo que um desenvolvedor tenha tempo de rodar o código. A maneira como um script de pré-instalação funciona da seguinte maneira: antes que quaisquer arquivos do pacote sejam colocados na sua pasta, o script pode realizar verificações no ambiente para determinar se deve executar um comportamento malicioso. Se o script detectar um ambiente protegido ou monitorado, ele se comunica com o servidor de origem silenciosamente e permanece inativo para evitar ser detectado.
A script de pós-instalação aguarda até que a porta seja destrancada e, então, uma infinidade de coisas pode acontecer. Um script de pós-instalação pode roubar segredos ou, nas palavras do E.T., ligar para casa e avisar ao servidor que a porta está de pé e que qualquer um pode entrar. Você pode comparar isso à lenda dos vampiros. Uma vez que você convida um vampiro para entrar, a caçada está aberta.
Assim que o script estiver dentro do seu node_modules, já é tarde demais – ele é executado imediatamente. O script executa um comando como curl ou wget para enviar seu .env , chaves SSH ou tokens da AWS para o site do invasor. Depois que ele tiver essas credenciais, não há muito o que você possa fazer além de excluir suas chaves/tokens e gerar novos.
Correção da confusão de dependências
Existem algumas maneiras diferentes de nos anteciparmos a esses problemas e garantirmos que o projeto esteja seguro e com todas as correções aplicadas.
Pacotes com escopo
A maneira mais clara de evitar um ataque de confusão de dependências é registrar sua organização no gerenciador de pacotes e usar pacotes com escopo, @myorg/mypackage. Isso proporciona um pacote seguro e privado que pode ser usado em seus próprios projetos em toda a organização.
Correto .npmrc Configurações
Ao criar seu arquivo .npmrc , certifique-se de mapear rigorosamente escopos específicos para registros privados. Ao garantir que eles sejam mapeados rigorosamente, isso impedirá que sejam “vazados” para o registro público. Um exemplo de um .npmrc configurado para isso é o seguinte, e essa é a maneira mais eficaz de combater problemas em seu projeto:
# Definir o registro padrão para pacotes públicos registry=https://registry.npmjs.org/ # Forçar todos os pacotes com escopo @org a serem resolvidos SOMENTE por meio do registro privado @org:registry=https://npm.pkg.github.com/ always-auth=true
Usando –ignore-scripts
Another clever way to get away from the preinstall and postinstall scripts is to use –ignore-scripts when installing packages. Using that flag looks like this: npm install <new-package> –ignore-scripts. This isn’t just something that is used once; you want to bake this into your automation and workflows as well.
Integridade do arquivo de bloqueio
Seu package-lock.json ou yarn.lock faz mais do que apenas fixar versões; ele armazena um hash, um hash criptográfico. Se o hash mudar e a compilação falhar, isso indica que um invasor trocou o pacote no registro. Usando o npm ci faz com que o arquivo de bloqueio seja aplicado. Com npm install, essa proteção pode ser contornada.
Proteja a pilha
Levando tudo isso em consideração, certifique-se de seguir uma lista de verificação de segurança rigorosa. Esses itens são apenas uma parte dela.
- Use pacotes com escopo e certifique-se de registrar sua organização no repositório
- Configure seu arquivo .npmrc para mapear rigorosamente seus pacotes para o registro privado
- Use –ignore-scripts em seus pipelines de CI/CD para desativar a pré-instalação e pós-instalação por padrão
A segurança da cadeia de suprimentos não é uma tarefa do tipo “configure e esqueça”, como um forno com espeto giratório da Ronco; ela exige vigilância constante à medida que seu projeto evolui. É algo que precisa ser cuidado desde o início e por muito tempo à frente, à medida que o projeto evolui.
Proteja seus registros, verifique seus escopos e monitore o que está acontecendo nas profundezas da sua árvore de dependências. Os maiores riscos nem sempre são visíveis – mas, muitas vezes, já estão presentes na sua pilha de tecnologia. Seguindo a mesma linha da metáfora do vampiro que não foi convidado a entrar, deixo para vocês esta citação do filme *The Lost Boys*: “Uma coisa sobre morar em Santa Carla que eu nunca consegui engolir: todos aqueles malditos vampiros.” Santa Carla é o software, e os vampiros são as falhas de segurança. Fique um passo à frente e não os deixe entrar.

