Skip to content
Back to Blog
·10 min read·technology

Dependency Confusion and Typosquatting: A Registry Attack Guide

How dependency confusion and typosquatting hijack npm, PyPI, Maven and NuGet builds, and the scoped-package, lockfile, and CI defences that stop them.

BR

Bachao.AI Research Team

Cybersecurity Research

Test Your Application

Security exposure this creates

Unpatched vulnerabilities in your tech stack are the #1 entry point for breaches targeting Indian businesses. Here's what to watch.

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:

    1. Public package.json / requirements.txt files committed to open-source forks or public GitHub repos
    2. Error messages, stack traces, or job postings that reference internal library names
    3. Publicly readable CI configuration files or Dockerfiles
    4. npm's public error responses, which historically could reveal whether a scoped package name existed internally
Once the attacker has the name, they publish a package with that exact name to the public registry, using a version number higher than the internal one — for example, internal 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.
🚨
DANGER
This is not a theoretical edge case. Birsan's February 2021 research reported successful dependency-confusion package execution inside build environments at multiple major technology companies using this exact technique, and the technique has since been repeated by both researchers and real attackers against other organisations. It works because it targets a default behaviour of the tooling, not a bug in any single package.

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.

🛡️
SECURITY
A CI/CD runner is frequently the single most privileged machine in an organisation's software delivery chain — it can hold deployment credentials, signing keys, and access to production infrastructure. Treat the dependency-install step of every pipeline as an untrusted code execution surface, not a routine housekeeping task.

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:

    1. 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-stream npm 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 2021 ua-parser-js npm 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.
    2. 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/liblzma compression library, caught before it reached most stable Linux distributions.
Feb 2021When Alex Birsan publicly demonstrated dependency confusion against major tech companies (Birsan research, 2021)
2018Year of the event-stream npm maintainer-handover incident (public npm/GitHub incident record)
2024Year the XZ Utils backdoor, CVE-2024-3094, was discovered before wide distribution (CVE record, 2024)

Know your vulnerabilities before attackers do

Run a free VAPT scan — takes 5 minutes, no signup required.

Book Your Free Scan

The attack flow, and where it gets blocked

graph TD A[Internal package name leaks via public manifest or repo] --> B[Attacker registers same name on public registry] B --> C[Attacker publishes higher version number than internal package] C --> D{Build resolver checks both private and public sources} D -->|Default config, highest version wins| E[Public malicious package resolved instead of private one] E --> F[Install script runs automatically on CI runner] F --> G[Attacker code executes with build credentials] G --> H[Credential and secret exfiltration from CI environment] D -->|Scoped package plus private-registry-first config| I[Private registry is authoritative source] I --> J[Internal package resolved correctly] J --> K[Public malicious package never fetched] style A fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style B fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style C fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style E fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style F fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style G fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style H fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style D fill:#1e3a5f,stroke:#3B82F6,color:#e2e8f0 style I fill:#1e3d2f,stroke:#10B981,color:#e2e8f0 style J fill:#1e3d2f,stroke:#10B981,color:#e2e8f0 style K fill:#1e3d2f,stroke:#10B981,color:#e2e8f0

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.

DefenceWhat it doesWhere to apply it
Scoped packages (@yourorg/package-name)Namespaces internal packages under an org-owned scope that a stranger cannot register on the public registrynpm, and equivalent group-ID namespacing in Maven
Private-registry-first resolutionExplicitly 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 hashesPins exact resolved versions and cryptographic hashes so a package cannot be silently swapped even if the registry serves something different laterpackage-lock.json / npm-shrinkwrap.json, requirements.txt with hashes, pom.xml with checksum verification
Disabling install scriptsPrevents arbitrary code execution at install time; install still happens, but nothing runs automaticallynpm install --ignore-scripts in CI, pip install --no-binary review, auditing setup.py/postinstall before allowing them
Internal namespace reservationProactively registering your internal package names (even as empty placeholder packages) on the public registry so an attacker cannot claim themOne-time registration per internal package name across npm, PyPI
💡
TIP
Ordering matters more than tooling here. A scoped package name with private-registry-first resolution closes the confusion vector even if you never touch install scripts. Disabling install scripts closes the RCE vector even if a confusion incident somehow still occurs. Layer both — do not pick one.
⚠️
WARNING
A lockfile alone does not fully protect a first-time install. If a developer or CI job runs 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:

  1. Audit every package.json, requirements.txt, and pom.xml for internally-named packages that are not yet scoped or namespace-reserved.
  2. Configure registry resolution explicitly — do not rely on default "check everywhere, take the highest version" behaviour.
  3. Add --ignore-scripts (or the ecosystem equivalent) to CI install steps, re-enabling only for specific, reviewed packages that genuinely require a build step.
  4. Commit and enforce lockfiles in CI — fail the build if the lockfile is missing, stale, or would resolve a different version than committed.
  5. Reserve internal package names on public registries as a low-effort, permanent block against future squatting.
The OpenSSF (Open Source Security Foundation) and OWASP both publish supply-chain security guidance covering these controls in more depth; see openssf.org and owasp.org for current recommendations, and nist.gov for NIST's software supply-chain guidance under SP 800-218 (SSDF).
🎯Key Takeaway
Dependency confusion and typosquatting are not exotic zero-days — they exploit ordinary, default package-manager behaviour plus the fact that install scripts execute automatically with no review step. The fix is entirely within an engineering team's control: scope internal packages, make private-registry resolution explicit rather than implicit, commit lockfiles with integrity hashes, disable install scripts in CI by default, and reserve your internal names on public registries before an attacker does. None of this requires waiting on a registry operator or a vendor patch.

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

pie showData title Illustrative supply-chain attack techniques against package registries "Dependency confusion" : 25 "Typosquatting" : 30 "Maintainer account takeover" : 25 "Malicious update to legitimate package" : 20

Figures above are illustrative of the general shape of documented supply-chain attack categories, not a measured statistic from a single named study.

Frequently Asked Questions

What is dependency confusion in simple terms?
It is a supply-chain attack where an attacker publishes a public package using the same name as an organisation's private internal package, with a higher version number, so that build tools configured to check both sources resolve and install the attacker's public package instead of the real internal one.
How is typosquatting different from dependency confusion?
Typosquatting relies on a developer or tool mistyping or misremembering a package name and installing a similarly-named malicious package by mistake, while dependency confusion exploits automatic version-resolution logic to override a correctly-typed internal package name with no typo involved.
Why do install scripts make this so dangerous?
Package managers like npm allow packages to run scripts automatically at install time with no separate approval step, so a malicious package can execute code — often with the build server's credentials and network access — the moment it is installed, before anyone reviews it.
What is starjacking?
Starjacking is when an attacker publishes a malicious package and links its registry metadata to a popular, unrelated legitimate open-source repository, borrowing that project's stars and reputation to make the malicious package appear more trustworthy.
What is the single most effective defence against dependency confusion?
Making private-registry resolution explicit rather than relying on default "highest version wins" behaviour, combined with scoping internal package names under an organisation-owned namespace that cannot be registered by a stranger on the public registry.
Should CI pipelines disable install scripts entirely?
Disabling install scripts by default in CI (for example npm's --ignore-scripts) and only re-enabling them for specific, reviewed packages that genuinely require a build step is a practical default that closes most of the automatic-code-execution risk without breaking legitimate builds.
BR

Bachao.AI Research Team

Cybersecurity Research

AI-powered security research and threat intelligence from the Bachao.AI team. Covering the latest vulnerabilities, CVEs, and cybersecurity developments affecting Indian businesses.

Get cybersecurity insights for Indian SMBs

Weekly vulnerability alerts, DPDP compliance tips, and security guides. No spam — unsubscribe anytime.

We respect your privacy. Your email is never shared.

Application-layer testing against the OWASP Top 10

Free automated scan — risk score in under 2 hours. No credit card required.

Test Your Application
Find your vulnerabilitiesStart free scan →