Hacktricks-skills dependency-confusion-audit
Audit and remediate dependency confusion vulnerabilities in package management configurations. Use this skill whenever the user mentions package managers (npm, pip, Maven, Gradle, NuGet, Go modules, Cargo, Bundler), CI/CD security, supply chain attacks, internal registries, or wants to secure their build dependencies. Also use when reviewing .npmrc, pip.conf, nuget.config, settings.xml, Gemfile, or any package configuration files. Make sure to use this skill when the user asks about securing dependencies, preventing supply chain attacks, or configuring private registries.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/dependency-confusion/SKILL.MDDependency Confusion Audit & Remediation
This skill helps you identify and fix dependency confusion vulnerabilities in your project's package management setup.
What is Dependency Confusion?
Dependency confusion occurs when a package manager resolves a dependency from an unintended, less-trusted registry (usually public) instead of the intended private/internal one. Attackers exploit this by publishing malicious packages with the same names as your internal dependencies to public registries.
Common root causes:
- Typosquatting: Importing
instead ofreqests
(resolves from public registry)requests - Missing internal packages: Importing
that doesn't exist internally, so the resolver falls back to public registriescompany-logging - Version preference: Internal
vs publiccompany-requests@1.0.1
— resolver picks the newer public versioncompany-requests@1.0.2
Quick Audit Checklist
Run through these checks to identify vulnerabilities:
- Registry Configuration: Are you mixing internal and public registries in the same resolution?
- Internal Namespaces: Do you have organization-specific package names (e.g.,
,@company/*
,company-*
)?Company.* - Fallback Behavior: Does your tooling fall back to public registries when internal packages aren't found?
- Version Pinning: Are versions pinned or locked, or do you allow wide ranges?
- Lockfile Integrity: Are lockfiles committed and enforced in CI?
Ecosystem-Specific Fixes
JavaScript/TypeScript (npm, Yarn, pnpm)
Problem: npm/yarn can resolve from multiple registries and pick the "best" version.
Fix: Bind internal scopes to private registry only.
.npmrc (project-level):
# Bind internal scope to private registry; do not allow public fallback for @company/* @company:registry=https://registry.corp.example/npm/ # Always authenticate to the private registry //registry.corp.example/npm/:_authToken=${NPM_TOKEN} strict-ssl=true
package.json (for internal package):
{ "name": "@company/api-client", "version": "1.2.3", "private": false, "publishConfig": { "registry": "https://registry.corp.example/npm/", "access": "restricted" } }
Yarn Berry (
.yarnrc.yml):
npmScopes: company: npmRegistryServer: "https://registry.corp.example/npm/" npmAlwaysAuth: true # CI should fail if lockfile would change enableImmutableInstalls: true
Operational tips:
- Only publish internal packages within the
scope@company - For third-party packages, allow public registry via your private proxy/mirror, not directly from clients
- Use
ornpm ci
in CIyarn install --immutable
Python (pip / Poetry)
Core rule: Don't use
--extra-index-url to mix trust levels. Either expose a single internal index that proxies and caches approved PyPI packages, or use explicit index selection and hash pinning.
pip.conf:
[global] index-url = https://pypi.corp.example/simple # Disallow source distributions when possible only-binary = :all: # Lock with hashes generated via pip-tools require-hashes = true
Generate hashed requirements with pip-tools:
# From pyproject.toml or requirements.in pip-compile --generate-hashes -o requirements.txt pip install --require-hashes -r requirements.txt
If you must reach public PyPI, do it via your internal proxy and maintain an explicit allowlist there. Avoid
--extra-index-url in CI.
.NET (NuGet)
Problem: Multiple package sources can be queried simultaneously.
Fix: Use Package Source Mapping to bind patterns to specific sources.
nuget.config:
<?xml version="1.0" encoding="utf-8"?> <configuration> <packageSources> <clear /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> <add key="corp" value="https://nuget.corp.example/v3/index.json" /> </packageSources> <packageSourceMapping> <packageSource key="nuget.org"> <package pattern="*" /> </packageSource> <packageSource key="corp"> <package pattern="Company.*" /> <package pattern="Internal.Utilities" /> </packageSource> </packageSourceMapping> </configuration>
Java (Maven/Gradle)
Problem: POMs can declare arbitrary repositories.
Fix: Mirror all to internal, ban ad-hoc repos.
Maven
settings.xml:
<settings> <mirrors> <mirror> <id>internal-mirror</id> <mirrorOf>*</mirrorOf> <url>https://maven.corp.example/repository/group</url> </mirror> </mirrors> </settings>
Add Enforcer plugin to ban repositories in POMs:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>3.6.1</version> <executions> <execution> <id>enforce-no-repositories</id> <goals><goal>enforce</goal></goals> <configuration> <rules> <requireNoRepositories /> </rules> </configuration> </execution> </executions> </plugin>
Gradle: Centralize and lock dependencies.
settings.gradle.kts:
dependencyResolutionManagement { repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS repositories { maven { url = uri("https://maven.corp.example/repository/group") } } }
Enable dependency verification and commit
gradle/verification-metadata.xml.
Go Modules
Problem: Public proxy can resolve private module names.
Fix: Mark private paths to skip proxy.
# Use corporate proxy first, then public proxy as fallback export GOPROXY=https://goproxy.corp.example,https://proxy.golang.org # Mark private paths to skip proxy and checksum db export GOPRIVATE=*.corp.example.com,github.com/your-org/* export GONOSUMDB=*.corp.example.com,github.com/your-org/*
Rust (Cargo)
Problem: crates.io fallback for internal packages.
Fix: Replace crates-io with internal mirror.
.cargo/config.toml:
[source.crates-io] replace-with = "corp-mirror" [source.corp-mirror] registry = "https://crates-mirror.corp.example/index"
For publishing, be explicit with
--registry and keep credentials scoped to the target registry.
Ruby (Bundler)
Problem: Multi-source Gemfiles can mix registries.
Fix: Use source blocks and disable multisource.
Gemfile:
source "https://gems.corp.example" source "https://rubygems.org" do gem "rails" gem "pg" end source "https://gems.corp.example" do gem "company-logging" end
Enforce at config level:
bundle config set disable_multisource true
CI/CD Hardening
- Single ingress: Use Artifactory/Nexus/CodeArtifact/GitHub Packages/Azure Artifacts as the only endpoint developers/CI can reach
- Immutable lockfiles: Fail CI if lockfile would change
- npm: commit
, usepackage-lock.jsonnpm ci - Yarn: commit
, useyarn.lockyarn install --immutable - Python: commit hashed
, enforcerequirements.txt--require-hashes - Gradle: commit
and fail on unknown artifactsverification-metadata.xml
- npm: commit
- Egress control: Block direct access from CI to public registries except via the approved proxy
- Name reservation: Pre-register your internal names/namespaces in public registries where supported
- Package provenance: When publishing public packages, enable provenance/attestations to make tampering more detectable
When to Use This Skill
Use this skill when:
- Reviewing package configuration files (
,.npmrc
,pip.conf
,nuget.config
,settings.xml
, etc.)Gemfile - Setting up a new project's dependency management
- Auditing CI/CD pipelines for supply chain security
- Investigating a potential dependency confusion incident
- Wanting to harden your build environment against supply chain attacks
- Configuring private registries or internal package management