
🤖 Ghostwritten by Claude Opus 4.6 · Fact-checked & edited by GPT 5.4
Dependency confusion is a software supply-chain attack in which a package manager installs a public package that shares the name of an internal package. The risk is highest when teams use unscoped private package names or misconfigure registry resolution, allowing a public package with the same name — often at a higher version — to win dependency resolution. The result can be silent substitution: the install succeeds, but the code came from the wrong source.
That risk is not theoretical. In late May 2026, Microsoft reported a campaign involving malicious npm packages published under multiple accounts and named to resemble internal packages used by several companies. According to Microsoft's write-up, the packages gathered host and environment details during installation. Whether a specific project was vulnerable depended on its package naming and registry configuration, not just on whether it used npm.
If you use private packages, CI pipelines, or AI-assisted coding tools that add dependencies quickly, dependency confusion deserves immediate attention. The core fix is simple: make sure internal package names are scoped and that each private scope is pinned to the correct private registry.
TL;DR: Dependency confusion happens when tooling resolves a package name from the wrong registry, causing a public package to replace an internal one.
A common explanation is that an attacker publishes a package with the same name as a company's internal package and a higher version number. That captures the basic idea, but the important detail is configuration. Modern package managers do not blindly prefer public packages in every setup. The attack works when a project's registry rules, package naming, or proxy behavior allow the public registry to be considered for a package that should have been private-only.
A typical vulnerable pattern looks like this:
This is different from typosquatting.
lod-ash instead of lodash.In short: typosquatting tricks people; dependency confusion tricks tooling.
TL;DR: The package name can look legitimate, the install can succeed normally, and malicious code may run before the application starts.
Dependency confusion is dangerous because it often leaves few obvious signs during development.
With typosquatting, a careful reviewer might catch a suspicious spelling. With dependency confusion, the package name may match the expected internal dependency exactly. That removes one of the easiest human checks.
npm supports lifecycle scripts such as preinstall, install, and postinstall. If a malicious package includes one of those scripts, code can run as soon as npm install executes. That means the package does not need to be imported by the application to do damage.
In the late-May 2026 campaign Microsoft described, the reported behavior included install-time reconnaissance such as collecting host and environment information. That is consistent with how npm lifecycle scripts work.
A lockfile records what was resolved, including versions and tarball sources. That makes it useful for review and incident response. But a lockfile is not a primary defense against dependency confusion. If the wrong package is resolved and committed, the lockfile will faithfully preserve that mistake.
| Attack type | What is impersonated | How it works | Main clue |
|---|---|---|---|
| Typosquatting | Popular public package | Misspelled package name | Name looks slightly off |
| Dependency confusion | Private/internal package | Wrong registry or namespace wins resolution | Correct name, wrong source |
| Maintainer compromise | Legitimate public package | Real package account is hijacked | Name and source look normal |
TL;DR: Use private scopes, pin each scope to the correct registry, review lockfiles, and limit install-script execution where practical.
For npm-based projects, the safest baseline is to use scoped internal package names such as @yourcompany/auth-utils and explicitly map that scope to the private registry.
## .npmrc
@yourcompany:registry=https://your-private-registry.example.com/That tells npm where packages in that scope should come from. In practice, this is one of the most effective defenses because it reduces ambiguity during resolution.
Two caveats matter:
Unscoped names are easier to collide with on public registries. If an internal package is named auth-utils instead of @yourcompany/auth-utils, the chance of confusion rises sharply.
Migrating old packages to a private scope can be tedious, but it closes a major gap.
Treat lockfile diffs as security-relevant changes, not generated noise. Reviewers should look for:
This is especially important in monorepos, where a small dependency change can affect many workspaces.
If a workflow does not require lifecycle scripts, disabling them reduces immediate risk.
npm install --ignore-scriptsThis is often useful in CI, sandbox analysis, or first-pass dependency inspection. It is not a complete fix: the malicious package may still be present on disk, and some legitimate packages rely on install scripts. But it can block the most common execution path.
Teams should periodically check whether internal package names are exposed to public collision. For npm ecosystems, that means confirming that internal names are scoped and that no legacy unscoped names remain in active use.
If a public package already exists with a name your internal tooling expects, treat that as a configuration review trigger.
TL;DR: An AI assistant can speed up the audit, but it should verify registry configuration and lockfile sources — not just list dependencies.
If you use an AI coding assistant, ask it for a project-specific dependency confusion review instead of a generic package audit. For example:
Scan this project's package.json files, workspace manifests, .npmrc, and lockfile.
Identify dependencies that appear to be private or internal packages.
For each one:
1. Confirm whether it uses a private scope.
2. Check whether that scope is pinned to the intended private registry.
3. Flag any unscoped internal package names.
4. Verify from the lockfile which registry host each package resolves from.
5. List any lifecycle scripts used by direct dependencies or newly added transitive dependencies.
Then summarize the packages most at risk of dependency confusion and show the exact configuration changes needed to reduce that risk.That prompt is useful because it asks for evidence from configuration and lockfiles, not just a guess based on package names.
It is a supply-chain attack where a package manager pulls a package from the wrong place. A project expects a private package, but because of naming or registry configuration issues, it installs a public package with the same name instead.
No. Typosquatting depends on a spelling mistake in a public package name. Dependency confusion uses the correct package name and exploits how registries and package managers resolve it.
Not always. Version number matters only if the package manager is actually considering both sources for the same dependency. Correct scope-to-registry mapping can prevent the public package from being considered at all.
Not by itself. A lockfile improves reproducibility and helps reviewers spot unexpected source changes, but it does not replace proper registry configuration.
Because they run during installation. An attacker does not need the application to import the package; the package manager may execute the script automatically as part of the install process.
npm install.Dependency confusion remains one of the clearest examples of how small configuration gaps can create outsized supply-chain risk. The attack does not require a dramatic exploit chain; it often succeeds because package naming, registry routing, and dependency review were treated as convenience settings instead of security controls. Teams that scope internal packages, pin registries explicitly, and review lockfile changes as part of normal engineering hygiene can reduce that risk substantially.
Discover more content: