Add a line of the form @yourscope:registry=https://your-registry.example to the project's .npmrc. npm then fetches every package in that scope from that URL, not from the public registry. Scope the auth token to the same host so credentials are never sent elsewhere.
The configuration
The npm documentation gives this form for a scoped registry:
# .npmrc in the project root
@acme:registry=https://registry.internal.example/
//registry.internal.example/:_authToken=${ACME_NPM_TOKEN}
Line one maps the scope. Line two supplies a token for that host only, read from an environment variable so the secret is not committed.
The documentation says credential settings must be scoped to a specific registry, and shows a bare token setting as a bad configuration. A path prefix limits a token further: a token at the host level applies to every scope on that host, while one at a path applies only to that path's scope.
Where the file lives
npm reads, in the order the documentation lists them, a per-project file next to package.json, a per-user file in your home directory, a global file, and a built-in one. Settings resolve by priority, and a user setting overrides the same global one.
Put the scope mapping in the project file and commit it. That way CI and every developer resolve the scope the same way. Keep the token itself out of the repository.
Why this blocks one kind of confusion
Without a mapping, a request for @acme/billing goes to the default registry. With it, the request for any @acme package goes only to your registry. An attacker cannot win by publishing a same-named package publicly, because npm never asks there.
Note what it does not cover:
- Unscoped internal names. The mapping applies to a scope. Move internal packages under a scope.
- Lockfile hosts. The config page describes a setting for replacing the registry host recorded in a lockfile. Review how your lockfile and registry host settings interact so you know where installs really come from.
- Other ecosystems. Each has its own control. See how to protect your package names.
Verify it works
npm config get @acme:registry
npm view @acme/billing dist.tarball
The second command prints the tarball URL. It should point at your registry host. Then block the private host in a test environment and confirm the install fails rather than falling back to the public registry.
A short rollout plan
- Move any unscoped internal package under your scope first. Rename it, publish it to your registry, and update the imports.
- Commit the scope mapping and the token reference to the template your new repositories start from.
- Add a CI step that prints the configured registry for your scope and fails if it is not your host.
- Audit existing lockfiles for packages in your scope whose resolved URL points at the public registry.
Do this once and new projects inherit the safe default.
Quick check
Can I commit the .npmrc file?
Commit the scope mapping. Do not commit a literal token. Reference an environment variable, as in the example above.
Does this replace claiming my scope publicly?
No. Mapping protects your builds. Claiming the scope on the public registry stops others from using it at all. Do both.
To check where your dependencies resolve from, run a free scan. Safeguard does SBOM and dependency scanning for npm projects.
Sources
Last reviewed: 19 October 2026.