Skip to content
← Blog

Technical explainer

npm and PyPI private registry security

3 min read

Many organizations run private npm or PyPI package registries — JFrog Artifactory, Sonatype Nexus, Verdaccio, or similar tools. Private registries offer benefits: caching for speed, control over which packages are available internally, and hosting for proprietary packages. They also introduce security risks that public registries don't have.

The caching risk

Private registries typically operate as transparent proxies — they cache packages from the public registry and serve them to developers without re-downloading from the public source on every install.

The security implication: if the public registry served a malicious version of a package at the time the private registry cached it, the private registry continues to serve that malicious version indefinitely — even after the public registry removes it.

Scenario: a malicious lodash@4.17.22 is published and exists on npm for 3 hours before being removed. During those 3 hours, your Artifactory instance caches lodash@4.17.22. After npm removes the malicious version, lodash@4.17.22 disappears from the public registry. But your Artifactory instance still has it — and will serve it to every developer and CI runner that installs lodash@4.17.22 through your private registry.

The re-verification problem

Most private registries don't re-verify hashes against the public registry after caching. They cached a tarball; they serve that tarball. If the tarball was malicious when it was cached, it remains malicious in the cache.

npm's integrity field in package-lock.json provides some protection — if a developer's lockfile contains the correct hash for the legitimate version, an install from the private registry that returns a different tarball would fail the hash check. But this only works if the developer has a lockfile with the correct hash, and the private registry is serving a tarball with a different hash.

If the developer doesn't have a lockfile (or the lockfile doesn't include the package), they get whatever the private registry has — and they have no way to know if it's the legitimate version.

The access control problem

Private registries require authentication. If authentication credentials are stored insecurely (in .npmrc or pip.conf without proper permissions, as plain environment variables in CI, or in shared credential stores), they can be compromised and used to:

  • Publish malicious internal packages
  • Access internal package source code
  • Read internal package metadata (which may reveal internal project structure)

Secure private registry configuration

For npm / Artifactory / Nexus:

# .npmrc — per-project configuration
registry=https://your-artifactory.acme.com/artifactory/api/npm/npm-local/
//your-artifactory.acme.com/artifactory/api/npm/npm-local/:_authToken=${ARTIFACTORY_TOKEN}
always-auth=true

Use environment variable substitution (${ARTIFACTORY_TOKEN}) rather than hardcoding tokens in .npmrc. Store the actual token value in a secrets manager, not in the .npmrc file.

For pip / Artifactory / Nexus:

# pip.conf
[global]
index-url = https://user:${ARTIFACTORY_TOKEN}@your-artifactory.acme.com/artifactory/api/pypi/pypi-local/simple/
trusted-host = your-artifactory.acme.com

Disable scope fallback to the public registry:

# .npmrc — prevent fallback to public registry for scoped packages
@acme:registry=https://your-artifactory.acme.com/artifactory/api/npm/npm-local/
@acme:always-auth=true
# Disable fallback for all packages through this registry:
registry=https://your-artifactory.acme.com/artifactory/api/npm/npm-local/

Configure regular cache invalidation:

In Artifactory and Nexus, configure cached packages to expire and be re-fetched from the public registry at regular intervals. A 24-hour cache TTL means that a malicious package removed from npm within 24 hours will eventually be evicted from your cache. This is imperfect but better than indefinite caching.

What Veln adds on top of private registries

Veln operates between your package manager and your private registry — just as it operates between your package manager and the public registry. It runs the same three-tier analysis pipeline regardless of where the tarball comes from.

Critically: Veln's consensus check compares the hash of whatever your private registry serves against the community hash (from Veln Consensus, derived from the public registry). If your private registry is serving a malicious cached version, the hash will differ from the community consensus, and Veln will flag it — even if the malicious version was removed from the public registry months ago.

This is one of the few defenses that catches the "stale malicious cache" scenario described above.


Private registries can silently serve stale malicious packages. Veln's consensus check catches hash divergence regardless of where the tarball comes from.