Dependency confusion is a supply-chain attack where a public package registry (npm, PyPI, Maven Central, NuGet) is tricked into serving an attacker's package instead of your organisation's private internal package, because most build tools resolve the highest version number across all configured sources by default — and an attacker who learns your internal package name from a leaked manifest can simply publish a higher-versioned public package with the same name. Typosquatting is the related trick of publishing a malicious package under a name one keystroke away from a popular legitimate one. Both are dangerous specifically because npm, PyPI, and similar registries allow arbitrary install-time scripts, which means a poisoned package can execute attacker code on your build server or CI runner the instant npm install or pip install runs — before any code review, before any security scan, before a human ever sees a diff.
How dependency confusion actually works
Every modern software project pulls in dozens or hundreds of third-party packages through a package manager. Large organisations also publish internal, private packages — shared libraries, internal SDKs, auth wrappers — hosted on a private registry, referenced by name in package.json, requirements.txt, or pom.xml, exactly like public dependencies.
The attack, publicly demonstrated by security researcher Alex Birsan in February 2021 against a number of large technology companies, exploits a simple resolution ambiguity: if a build tool checks both a private registry and the public registry (npm, PyPI) for the same package name without being told which source is authoritative, many default configurations fetch whichever source offers the highest semantic version number — even if that source is the public internet and the package was published five minutes ago by a stranger.
An attacker does not need to breach your network to run this. They only need your internal package name, which routinely leaks through:
- Public
package.json/requirements.txtfiles committed to open-source forks or public GitHub repos - Error messages, stack traces, or job postings that reference internal library names
- Publicly readable CI configuration files or Dockerfiles
- npm's public error responses, which historically could reveal whether a scoped package name existed internally
9.2.1 becomes a public 99.0.0. On the next npm install, pip install, or CI build, the resolver may pull the attacker's package instead of the real internal one.
Why install scripts turn this into instant RCE
The step that makes dependency confusion so dangerous is not the download — it is what happens immediately after. npm, and to a lesser extent other ecosystems, allow packages to define lifecycle scripts (preinstall, install, postinstall) that run automatically, with no prompt, the moment the package is installed. PyPI's setup.py can execute arbitrary Python at install time in a similar way.
This means the moment your CI pipeline runs a routine npm install or pip install -r requirements.txt, the attacker's code executes with whatever privileges that build job holds — typically read access to source code, environment variables, and cloud credentials injected for deployment. There is no separate "run" step for the attacker to trigger. The install itself is the exploit.
Typosquatting and starjacking: the social-engineering cousin
Where dependency confusion exploits resolver logic, typosquatting exploits human attention. An attacker publishes a package with a name nearly identical to a popular one — a swapped letter, a missing hyphen, a common misspelling — hoping a developer typing quickly, or an AI coding assistant hallucinating a plausible-sounding package name, installs the wrong one. The malicious package often mirrors the real package's functionality closely enough to avoid immediate suspicion while quietly exfiltrating environment variables or credentials on install.
Starjacking is a related trick: an attacker publishes a malicious package and points its registry metadata (GitHub repository link, stars, README) at a legitimate, popular, unrelated open-source project, borrowing that project's reputation and star count to make the malicious package look trustworthy to anyone glancing at the registry page before installing.
These techniques sit alongside two other well-documented supply-chain attack patterns worth distinguishing:
- Maintainer-trust abuse, where an attacker gains publishing control over an already-trusted package — either by taking over compromised account credentials, or by socially engineering the existing maintainer into handing over maintainership — and then pushes a malicious update. The 2018
event-streamnpm incident is a documented handover case: the original maintainer, no longer active on the project, transferred publish rights to a new volunteer contributor, who then added a malicious dependency (flatmap-stream) targeting a cryptocurrency wallet. The 2021ua-parser-jsnpm compromise is a documented account-takeover case: an attacker gained control of the maintainer's npm account credentials and published versions containing cryptomining and credential-stealing payloads. - Long-game maintainer-trust attacks, where an attacker spends years building legitimate contribution history before inserting a backdoor. The 2024 XZ Utils backdoor (CVE-2024-3094) is the clearest documented case: a contributor who had gained co-maintainer trust over roughly two years inserted an obfuscated backdoor into the widely used
xz/liblzmacompression library, caught before it reached most stable Linux distributions.
Know your vulnerabilities before attackers do
Run a free VAPT scan — takes 5 minutes, no signup required.
Book Your Free ScanThe attack flow, and where it gets blocked
The split at the resolver step is the point of the defences below: this is a configuration default, fixable at the configuration layer without waiting on the registry operator.
The concrete defences that actually close this
None of these require exotic tooling — they are configuration and process changes available today in npm, pip, Maven, and NuGet.
| Defence | What it does | Where to apply it |
|---|---|---|
Scoped packages (@yourorg/package-name) | Namespaces internal packages under an org-owned scope that a stranger cannot register on the public registry | npm, and equivalent group-ID namespacing in Maven |
| Private-registry-first resolution | Explicitly pins which registry is authoritative for which package or scope, instead of "highest version wins" across all sources | .npmrc scoped registry mapping, pip --index-url with --no-index fallback disabled, Maven/NuGet repository priority |
| Lockfiles with integrity hashes | Pins exact resolved versions and cryptographic hashes so a package cannot be silently swapped even if the registry serves something different later | package-lock.json / npm-shrinkwrap.json, requirements.txt with hashes, pom.xml with checksum verification |
| Disabling install scripts | Prevents arbitrary code execution at install time; install still happens, but nothing runs automatically | npm install --ignore-scripts in CI, pip install --no-binary review, auditing setup.py/postinstall before allowing them |
| Internal namespace reservation | Proactively registering your internal package names (even as empty placeholder packages) on the public registry so an attacker cannot claim them | One-time registration per internal package name across npm, PyPI |
npm install without a private-registry-first configuration on a machine that has never resolved that package before, the lockfile only helps once it has been generated correctly the first time. Generate and commit lockfiles from a controlled environment with correct registry precedence already configured.Building this into CI/CD, not just developer laptops
The highest-value place to apply these controls is the CI/CD pipeline, because that is where install-time code execution has the most privileged blast radius. A practical rollout for an Indian engineering team:
- Audit every
package.json,requirements.txt, andpom.xmlfor internally-named packages that are not yet scoped or namespace-reserved. - Configure registry resolution explicitly — do not rely on default "check everywhere, take the highest version" behaviour.
- Add
--ignore-scripts(or the ecosystem equivalent) to CI install steps, re-enabling only for specific, reviewed packages that genuinely require a build step. - Commit and enforce lockfiles in CI — fail the build if the lockfile is missing, stale, or would resolve a different version than committed.
- Reserve internal package names on public registries as a low-effort, permanent block against future squatting.
For Indian SMBs and growing engineering teams, this class of risk sits squarely inside the software supply chain that a broader vulnerability assessment should cover, alongside dependencies, CI/CD configuration, and exposed credentials. A free VAPT scan can help surface exposed build infrastructure and misconfigured internet-facing services as part of a wider supply-chain review; Dhisattva AI Pvt Ltd, the company behind Bachao.AI, builds continuous vulnerability visibility so that these gaps are found before an attacker does. For more on supply-chain and dependency risk, see the blog.
Illustrative attack-technique mix
Figures above are illustrative of the general shape of documented supply-chain attack categories, not a measured statistic from a single named study.