In a Node template, commit a project .npmrc that maps your organization's scope to your private registry, and publish every internal package under that scope. That way an internal name is never looked up on the public registry by accident.
What scopes do
npm's documentation says a scope allows you to create a package with the same name as a package created by another user or organization without conflict. The scope is the part between the @ and the slash, as in @yourorg/package-name. It also states that unscoped packages are always public, and private packages are always scoped.
That second fact is the useful one for a platform team. If an internal package has an unscoped name, someone can publish a public package with the same name, and a misconfigured install may pick it up. This class of problem is usually called dependency confusion. See dependency confusion and private registries for the background.
What to put in the template
| Item | Why |
|---|---|
| Project level .npmrc | Applies to everyone who clones the repo, including CI |
| Scope to registry mapping | Routes @yourorg packages to the private registry |
| No credentials in the file | Tokens come from CI secrets or the developer's own user config |
| Lockfile committed | Records exactly which registry and version resolved |
| npm ci in CI | Strict install, fails on drift |
npm reads configuration from several .npmrc locations, including per project and per user files. Check the npmrc documentation for the order of precedence before you rely on one file overriding another.
Steps
- Choose one scope for all internal packages and reserve the same name on the public registry if you can, so nobody else can claim it.
- Rename any internal unscoped packages. Publish the old names as deprecated pointers during transition if consumers need time.
- Add the scope mapping to the template .npmrc.
- Configure CI to authenticate with a short lived or narrowly scoped token.
- Add a check that fails the build if a dependency in package.json is unscoped but present in your internal package list.
- Test from a clean machine: a fresh clone should install without any personal configuration.
Mistakes to avoid
- Committing a token in .npmrc. Use environment variable references or CI secrets.
- Pointing the default registry at a private proxy without deciding what it does for packages it does not hold. Know whether it falls through to the public registry.
- Leaving old unscoped internal names in older services.
- Assuming a lockfile alone fixes this. It records what was resolved, but the first resolution still needs to be correct.
Verify with a clean install
The most useful test is a new developer laptop or a clean container. If install works only because of local configuration, the template has a gap that will bite the next team.
Where Safeguard fits
Safeguard scans your resolved dependencies, applies policies and gates to changes, and monitors components over time, so unexpected packages show up in the inventory. Try a free scan at gold.safeguard.sh/scan.
Try it yourself
Open the inventory and look for packages whose source or name you do not recognize, which is the signal a scope mistake leaves behind.
Click through it at your own pace, or open it full screen.
Quick check
Are unscoped packages ever private?
Per npm's documentation, unscoped packages are always public and private packages are always scoped.
Can the template .npmrc hold the auth token?
It should not. Keep credentials in CI secrets or user level configuration.
Sources
Last reviewed: 19 October 2026.