HTTP tarball dependencies (and git-hosted tarballs) are stored in the lockfile without integrity hashes. This allows the remote server to serve different content on each install, even when a lockfile is committed.
When a package depends on an HTTP tarball URL, pnpm's tarball resolver returns only the URL without computing an integrity hash:
resolving/tarball-resolver/src/index.ts:
return {
resolution: {
tarball: resolvedUrl,
// No integrity field
},
resolvedVia: 'url',
}
The resulting lockfile entry has no integrity to verify:
remote-dynamic-dependency@http://example.com/pkg.tgz:
resolution: {tarball: http://example.com/pkg.tgz}
version: 1.0.0
Since there is no integrity hash, pnpm cannot detect when the server returns different content.
This affects:
- HTTP/HTTPS tarball URLs ("pkg": "https://example.com/pkg.tgz")
- Git shorthand dependencies ("pkg": "github:user/repo")
- Git URLs ("pkg": "git+https://github.com/user/repo")
npm registry packages are not affected as they include integrity hashes from the registry metadata.
See attached pnpm-bypass-integrity-poc.zip
The POC includes:
- A server that returns different tarball content on each request
- A malicious-package that depends on the HTTP tarball
- A victim project that depends on malicious-package
To run:
cd pnpm-bypass-integrity-poc
./run-poc.sh
The output shows that each install (with pnpm store prune between them) downloads different code despite having a committed lockfile.
An attacker who publishes a package with an HTTP tarball dependency can serve different code to different users or CI/CD environments. This enables:
The attack requires the victim to install a package that has an HTTP/git tarball in its dependency tree. The victim's lockfile provides no protection.
{
"severity": "HIGH",
"github_reviewed_at": "2026-01-07T19:06:59Z",
"cwe_ids": [
"CWE-494"
],
"nvd_published_at": "2026-01-07T22:15:43Z",
"github_reviewed": true
}