Yes. If an internal package has an unscoped name and a public package with that name exists or is later published, a resolver that falls back to the public registry can install the wrong one. Use a scope you control, reference local packages with the workspace protocol, and claim your names publicly if you can.
Definition
Dependency confusion is when a build installs a public package instead of the private one you meant, because the names match. It is a naming and resolution problem, not a bug in any one tool.
What the docs say
npm's scope documentation explains that a scope acts as a namespace and allows you to create a package with the same name as a package created by another user or organization without conflict. It also says unscoped packages are always public, and private packages are always scoped.
pnpm's workspace documentation covers the second half. If a dependency's range matches a package in the workspace, pnpm may link it. If it does not, the version is installed from the registry, and the docs call this behavior uncertain. With the workspace: protocol, pnpm refuses to resolve anything other than a local workspace package, so an install fails instead of falling back.
Why migrations raise the risk
Before the move, an internal library such as shared-utils may have been published to a private registry with a pinned version. Inside a monorepo it becomes a workspace package. Services that still declare an old version range may no longer match the local one. The resolver then asks a registry for shared-utils, and if the registry setup allows public fallback, the answer could come from a stranger.
Steps
- List every package name in the workspace and check each against the public registry. A hit means someone already owns that name publicly.
- Rename internal packages into your scope, for example
@yourorg/shared-utils. Update imports in one mechanical change. - Use
workspace:for every internal dependency, so a mismatch is an error. - Mark packages that should never be published with
private: true, per private true and files stop accidental npm publish. - Configure the registry so scoped names resolve to your private source and cannot fall through to the public one.
- Where practical, register your scope on the public registry so no one else can.
- Add a CI check that fails when a lockfile resolves an internal name from a public source.
Related checks
Look at how installs behave in CI too: approved package allowlist or scan on install is a decision guide for install controls. And the migration diff in diff resolved versions after a workspace migration will show a registry-sourced internal name if one slipped in.
Safeguard's SBOM and dependency scanning list the source of each component, which helps you spot a package that should have been internal.
How to test your setup
A simple test proves more than a review. Create a throwaway package in a scratch workspace that depends on an internal name, remove the local package, and run an install in a clean environment. A safe setup fails with a clear error. An unsafe one installs something from a public source. Run the same test in CI once, so a change in registry settings is caught later.
Quick check
Does a private registry alone prevent this?
Not if the resolver can also reach the public one for the same name. Scope the names and control fallback.
Are scoped packages always private?
No. Scoped packages are private by default, but can be published publicly with a flag.
Sources
Last reviewed: 19 October 2026.