Scopes help a lot, but they are not a complete defense. An npm scope is a namespace owned by a user or organization, so a scoped internal package cannot be claimed by an outsider under the same scope. It does not protect unscoped names or bad registry configuration.
What a scope is
The npm documentation says that when you sign up for an account or create an organization, you are granted a scope matching that name. A scoped package name carries the scope before a slash, for example @acme/utils, where the scope is everything between the at sign and the slash.
A scope lets different owners use the same package name without conflict. Two organizations can each have a package called utils, as long as they sit under different scopes.
The docs also state some defaults: unscoped packages are always public, private packages are always scoped, and scoped packages are private by default. You must pass a flag when publishing to make a scoped package public.
Why this matters for internal packages
Dependency confusion happens when a build asks for an internal package name and the client ends up fetching a public package with the same name. If your internal packages are unscoped, anyone can register that name publicly, possibly with a higher version number.
If your internal packages live under your own scope, the public name @yourcompany/utils is controlled by you. Nobody else can publish under that scope on the public registry.
How to apply it
- Move internal packages under a scope you own. Register the organization name on the public registry even if you never publish there, so nobody else can take it.
- Map the scope to your private registry in your npm configuration, so requests for that scope go to the private source.
- Avoid unscoped internal names. If you must keep some, reserve those names on the public registry with a placeholder or document why not.
- Commit the registry mapping alongside the project so every developer and CI job resolves the same way.
- Check the lockfile for resolved URLs that point somewhere unexpected for your scoped packages.
Where scopes fall short
- Third party unscoped packages. Most popular packages are unscoped, and typo or lookalike names exist at that level.
- Misconfiguration. If a scope is not mapped, a request may go to the default registry. That is a failure of setup, not of scopes.
- Account takeover. Scope ownership depends on account security. Use strong authentication for every account that can publish.
- Copycat scopes. An outsider can register a scope that looks like yours. Be careful that the scope in a dependency is spelled exactly right.
A quick audit
List every dependency in your manifests whose name looks internal. For each, answer: is it scoped, is the scope mapped to the private registry, and is the public scope reserved? Any no is a to-do.
Safeguard scans dependencies and flags packages for review, which helps with the last point: finding names that look wrong. Treat flags as prompts for a person to check.
Quick check
Are scoped packages always private?
They are private by default, but you can publish them as public with a flag, according to the npm docs.
Does using a scope make my build immune to confusion attacks?
No. Unscoped dependencies and a missing registry mapping can still send requests to the wrong place.
Sources
Last reviewed: 19 October 2026.