A dependency confusion explora uma regra silenciosa da resolução de dependências: quando o mesmo nome de pacote existe em um índice privado e em um índice público, quem escolhe a versão é o gerenciador de pacotes, não a sua equipe. Equipes que publicam artefatos internos em registros privados são as diretamente afetadas, porque os nomes desses pacotes vazam com facilidade em lockfiles esquecidos, manifestos copiados para repositórios abertos e documentação de integração. A ação prioritária é eliminar a disputa entre índices: concentrar toda a resolução de dependências em um único feed privado, com fontes upstream controladas, e renomear os pacotes internos para um namespace de propriedade da organização.
Como o ataque funciona
O ataque começa pela descoberta de nomes. Manifestos de build publicados por engano, dependências de projetos internos que chegaram a repositórios abertos e bundles de configuração compartilhados fornecem ao atacante a lista de pacotes que só existem no registro privado da vítima. O passo seguinte é registrar esses mesmos nomes em um registro público, como o PyPI ou o npm, em versões com número mais alto do que a interna — e, com frequência, com scripts de instalação que executam código assim que o pacote é baixado.
Se o pipeline consulta o registro público junto com o privado, a versão recém-publicada pode vencer a resolução. O código malicioso roda então dentro do ambiente de integração contínua, onde costuma encontrar variáveis de ambiente com tokens de nuvem, credenciais de registro e chaves de deploy. O invasor não precisa quebrar servidor algum: a vítima baixa e executa o artefato por conta própria, com as permissões que o build já tinha. O impacto típico é roubo de segredos do runner, adulteração de artefatos de release e persistência em cadências futuras de build, porque a versão maliciosa passa a satisfazer a dependência declarada.
O alerta oficial do pip
A documentação do pip trata o problema como classe de segurança documentada, não como hipótese de pesquisa. O manual do comando de instalação classifica o uso da opção de índice adicional para buscar pacotes privados como inseguro e nomeia a técnica: dependency confusion. Um atacante publica um pacote com o mesmo nome em um índice público, e essa versão pode ser escolhida no lugar do pacote privado da organização.
A causa está no algoritmo de resolução: não existe prioridade entre os locais pesquisados. Todos os índices configurados são verificados em conjunto e a ferramenta seleciona a melhor versão encontrada segundo o número de versão, sem perguntar de onde ela veio. Enquanto a configuração do cliente somar fontes concorrentes, a resolução permanece uma disputa que o atacante consegue vencer apenas registrando um nome antes. Essa é a razão pela qual ajustar a ordem das opções de índice não resolve: o comportamento documentado não promete precedência ao índice principal.
Defesa com feed único
A recomendação consolidada dos provedores de registro corporativo é inverter o desenho: em vez de somar índices no cliente, concentrar tudo em um feed só. A orientação oficial do Azure Artifacts manda que a configuração de cada cliente referencie um único feed, com upstream sources habilitados quando também são necessários pacotes de registros públicos. O feed verifica as fontes upstream em sequência e devolve o pacote da primeira fonte que o contém, com ordem definida pelo administrador — pacotes publicados no próprio feed têm precedência, e o código externo só entra pela exceção configurada.
Esse desenho muda o modelo de ameaça: o cliente nunca negocia diretamente com o registro público, e o feed vira o único ponto de decisão. Combine isso com restrição de rede nos runners de build, que só alcançam o feed interno, e com o congelamento de versões por lockfile com verificação de hash. O objetivo final é a restauração previsível dos pacotes: mesma entrada, mesma origem, mesmo artefato, sem espaço para um nome duplicado decidir a disputa no meio do caminho.
Namespace próprio nos nomes
O segundo controle ataca o alvo do ataque: o nome plano do pacote. No npm, cada conta de usuário ou organização recebe um scope com o mesmo nome, que serve como namespace para pacotes relacionados e permite criar um pacote com o mesmo nome de outro usuário sem conflito. Publicar artefatos internos sempre sob o scope da empresa remove o alvo, porque o nome completo passa a incluir o namespace exclusivo da sua organização, combinação que terceiros não têm como registrar.
No ecossistema Python, que não tem scope nativo no nome, o equivalente é adotar um prefixo reservado em todos os pacotes internos e publicar um pacote sentinela com esse nome no índice público, ocupando a combinação e transformando qualquer tentativa de uso em sinal monitorável. O renomeio custa trabalho, mas é a única medida que sobrevive a mudanças de ferramenta: registry, cliente e pipeline podem trocar, e o namespace continua protegendo.
Checklist de implantação
Uma sequência testada para fechar a porta em ciclos curtos:
- Inventariar todos os arquivos de configuração de cliente (pip, npmrc, NuGet.config, settings.xml) e listar cada índice adicional em uso.
- Provisionar um feed único com upstream sources e apontar todos os clientes para ele, removendo os índices somados.
- Renomear pacotes internos para o namespace da organização, com janela de compatibilidade publicada internamente.
- Publicar pacotes sentinelas que ocupem os nomes internos ainda sem namespace nos registros públicos.
- Bloquear, na rede do CI, o acesso direto a registros públicos e exigir lockfile com hash verificado no pipeline.
- Monitorar os registros públicos pelos nomes internos, com alerta para qualquer publicação nova.
| Controle | O que entrega | Prioridade |
|---|---|---|
| Feed único com upstream sources | Elimina a disputa entre índices na resolução | P1 |
| Namespace ou prefixo reservado | Remove o nome plano como alvo registrável | P1 |
| Rede do CI restrita ao feed interno | Corta o acesso direto a registros públicos | P1 |
| Pacote sentinela no registro público | Ocupa o nome e gera sinal de tentativa | P2 |
| Lockfile com verificação de hash | Congela a origem e a versão de cada artefato | P2 |
Para elevar a garantia sobre o que o build produz, complemente com trilhas de proveniência e assinatura de artefatos: o guia sobre SLSA v1.2 e a segurança do código-fonte mostra como atestar a origem de cada release.
Sinais de comprometimento
Detectar dependency confusion exige olhar a resolução, não só o antivírus. Sinais que merecem investigação imediata: log de instalação mostrando pacote interno baixado de host público; versão de pacote interno que ninguém do time publicou aparecendo no lockfile; resolução bem-sucedida após falha de autenticação no registro privado, indicando que a fonte pública assumiu; e contas publicadoras desconhecidas nos metadados do artefato instalado.
Se o sinal confirmar execução no runner, trate como comprometimento do ambiente de build: isole o runner, revogue as credenciais que ele carregava, reconstrua a partir de lockfile conhecido e rotacione os segredos usados no pipeline. Como os tokens de nuvem são o prêmio mais comum, reduza o dano possível endurecendo o acesso a credenciais de instância — a prática descrita no guia IMDSv2 contra roubo de credenciais EC2 limita o que um script malicioso consegue extrair mesmo depois de executar.
Prioridades P1 e P2
P1: um único feed por cliente com upstream sources, remoção completa de índices adicionais das configurações e renomeio dos pacotes internos para namespace próprio. Essas três medidas, juntas, fecham a rota principal do ataque.
P2: pacotes sentinelas nos registros públicos, verificação de hash obrigatória em lockfiles do pipeline e monitoramento contínuo de publicações com nomes internos. São camadas que encurtam o tempo entre a tentativa e a detecção, e que sustentam a resposta quando a rota principal mudar de ferramenta.