Skip to content

Commit eaa2767

Browse files
1 parent 1b5b4f1 commit eaa2767

3 files changed

Lines changed: 240 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-54hh-g5mx-jqcp",
4+
"modified": "2026-06-26T22:52:33Z",
5+
"published": "2026-06-26T22:52:33Z",
6+
"aliases": [
7+
"CVE-2026-50573"
8+
],
9+
"summary": "pnpm: Unsafe default behavior breaks integrity check",
10+
"details": "While it is unclear whether this should be classified as a vulnerability, it is being reported through this channel because the current behavior may represent an unsafe default.\n\n## Summary\n\n`pnpm install` in non-frozen mode can accept new remote package content after detecting that the downloaded tarball does not match the integrity recorded in `pnpm-lock.yaml`.\n\nWhen a package is already locked with an `integrity` value, and the registry later serves different metadata and tarball content for the same package name and version, pnpm initially reports an integrity mismatch. However, plain `pnpm install` then performs a resolution repair, accepts the registry's new integrity, updates the lockfile, installs the new content, and exits successfully.\n\nThis means the lockfile integrity check does not act as a hard stop by default.\n\n## Reproduction Scenario\n\n1. Run a local npm-compatible registry.\n2. Publish or serve `example-package@1.0.0` with tarball content `v1`.\n3. Install it with pnpm:\n\n```bash\npnpm add example-package@1.0.0 --registry=http://127.0.0.1:48741\n```\n\n4. Confirm `pnpm-lock.yaml` contains the `v1` integrity:\n\n```yaml\npackages:\n example-package@1.0.0:\n resolution:\n integrity: sha512-...v1...\n```\n\n5. Change the registry metadata and tarball for the same `example-package@1.0.0` to content `v2`.\n6. On a clean store/cache, run:\n\n```bash\npnpm install --registry=http://127.0.0.1:48741\n```\n\n## Observed Behavior\n\npnpm detects the checksum mismatch:\n\n```text\nWARN Got unexpected checksum for \"http://127.0.0.1:48741/example-package/-/example-package-1.0.0.tgz\".\nWanted \"sha512-...v1...\"\nGot \"sha512-...v2...\".\n\nERR_PNPM_TARBALL_INTEGRITY The lockfile is broken! Resolution step will be performed to fix it.\n```\n\nHowever, the install still succeeds:\n\n```text\nINSTALL_RC=0\nINSTALLED=v2-replaced\n```\n\nThe lockfile is then rewritten to trust the new remote integrity:\n\n```yaml\npackages:\n example-package@1.0.0:\n resolution:\n integrity: sha512-...v2...\n```\n\n## Expected Behavior\n\nIf a downloaded tarball does not match the integrity recorded in `pnpm-lock.yaml`, the install should fail by default.\n\nThe lockfile integrity should be treated as authoritative unless the user explicitly requests lockfile repair or dependency update behavior.\n\n## Security Impact\n\nThis behavior weakens the protection normally expected from a committed lockfile.\n\nIf a registry is compromised and an attacker overwrites the metadata and tarball for an existing package version, a new environment without the old pnpm store/cache may install the attacker's replacement package even though the project already has a lockfile with the original integrity.\n\nExamples of affected new or clean environments include:\n\n- an engineer setting up the project on a new machine\n- a new team member onboarding to the project\n\nIn this situation, pnpm first detects that the downloaded tarball does not match the integrity stored in `pnpm-lock.yaml`. However, instead of failing by default, plain `pnpm install` performs a resolution repair, trusts the current remote registry metadata, updates the lockfile to the new integrity, and installs the new registry content.\n\nIn other words, when the lockfile and registry disagree, the default non-frozen behavior can end up trusting the remote registry over the content previously recorded in the lockfile.\n\nThis is especially relevant for:\n\n- private registries that allow overwriting or republishing the same version\n- registry mirrors or proxies that can serve changed metadata and tarballs\n- compromised public or private registries\n- compromised registry proxy infrastructure\n\nThe behavior is also surprising because the command reports an integrity error but still exits successfully after resolution repair.\n\nThis issue does not occur when `--frozen-lockfile` is enabled. In frozen mode, the same integrity mismatch fails the install and does not install the changed package content.\n\nHowever, since the lockfile already records an integrity value, the integrity for the same package version should normally not change. If it does change, one likely explanation is that the server or registry has been compromised or is serving mutated package content. Under normal package publishing workflows, changed package content should be published as a new version instead of replacing an existing version.\n\nFor that reason, it may be safer for pnpm's default behavior to be closer to frozen mode for this specific case. At minimum, pnpm should not automatically repair the lockfile and trust the registry after an integrity mismatch. It should fail and let the user explicitly decide whether to discard the locked integrity, re-resolve the package from the remote registry, and update the lockfile.\n\n## Comparison\n\nIn the same scenario, `npm install` with an existing `package-lock.json` fails with `EINTEGRITY` and does not install the changed tarball.\n\n`pnpm install --frozen-lockfile` also fails as expected:\n\n```text\nERR_PNPM_TARBALL_INTEGRITY\n```\n\nThe issue is specific to the default non-frozen behavior of plain `pnpm install` in non-CI environment.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "pnpm"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "10.34.0"
32+
}
33+
]
34+
}
35+
]
36+
},
37+
{
38+
"package": {
39+
"ecosystem": "npm",
40+
"name": "pnpm"
41+
},
42+
"ranges": [
43+
{
44+
"type": "ECOSYSTEM",
45+
"events": [
46+
{
47+
"introduced": "11.0.0"
48+
},
49+
{
50+
"fixed": "11.4.0"
51+
}
52+
]
53+
}
54+
]
55+
}
56+
],
57+
"references": [
58+
{
59+
"type": "WEB",
60+
"url": "https://github.com/pnpm/pnpm/security/advisories/GHSA-54hh-g5mx-jqcp"
61+
},
62+
{
63+
"type": "ADVISORY",
64+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-50573"
65+
},
66+
{
67+
"type": "PACKAGE",
68+
"url": "https://github.com/pnpm/pnpm"
69+
}
70+
],
71+
"database_specific": {
72+
"cwe_ids": [
73+
"CWE-345"
74+
],
75+
"severity": "MODERATE",
76+
"github_reviewed": true,
77+
"github_reviewed_at": "2026-06-26T22:52:33Z",
78+
"nvd_published_at": "2026-06-25T18:16:39Z"
79+
}
80+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-p4xf-rf54-rj3x",
4+
"modified": "2026-06-26T22:53:21Z",
5+
"published": "2026-06-26T22:53:21Z",
6+
"aliases": [
7+
"CVE-2026-50014"
8+
],
9+
"summary": "pnpm: Git Fetch Argument Injection via Lockfile resolution.commit",
10+
"details": "## Summary\n\npnpm passes the lockfile-controlled git `resolution.commit` value to `git fetch` without a `--` separator or commit-format validation. For git dependencies fetched through the shallow-fetch path, a malicious lockfile can replace the expected 40-character commit hash with a Git option such as `--upload-pack=<command>`. For SSH and local transports, `--upload-pack` can execute the supplied command. HTTPS transports ignore `--upload-pack`, so the practical attack surface is primarily SSH or local git dependencies.\n\n## Vulnerability Details\n\nThe vulnerable path is in `fetching/git-fetcher/src/index.ts`. When a git dependency host is configured for shallow fetching, pnpm calls:\n\n```typescript\nawait execGit(['fetch', '--depth', '1', 'origin', resolution.commit], { cwd: tempLocation })\n```\n\nBecause `resolution.commit` is appended before a `--` separator, Git can parse a commit value beginning with `-` as an option. The same file later passes the value to `git checkout` without a separator:\n\n```typescript\nawait execGit(['checkout', resolution.commit], { cwd: tempLocation })\n```\n\n`resolution.commit` comes from the lockfile and is typed as a plain `string`; pnpm does not validate it as a 40-character hexadecimal commit before passing it to Git.\n\n## Proof of Concept\n\n```bash\nbash autofyn_audit/exploits/vuln11_git_upload_pack_rce/exploit.sh\n# Creates a local bare git repo and triggers the shallow-fetch path.\n# Replaces the lockfile commit hash with '--upload-pack=touch /tmp/vuln11_pwned'.\n# Result: PASS -- /tmp/vuln11_pwned created by injected touch command.\n```\n\nThe PoC uses a local `file://githost/...` repository because the injection requires a local or SSH transport. HTTPS transport ignores `--upload-pack`.\n\n## Impact\n\nCode execution as the user running `pnpm install`, under specific transport conditions. The attacker must modify `pnpm-lock.yaml`, and the affected dependency must use SSH or local git transport. HTTPS transport (the common case) is immune.\n\n## Suggested Remediation\n\nAdd a `--` separator before lockfile-controlled git revision values. Validate `resolution.commit` matches `/^[0-9a-f]{40}$/i` before passing to Git.\n\n---\n\n> Discovered by [AutoFyn](https://github.com/SignalPilot-Labs/AutoFyn)\n> Full audit report: [audit_report.md](https://github.com/tempcollab/pnpm/blob/main/autofyn_audit/audit_report.md)\n> Exploit script: [exploit.sh](https://github.com/tempcollab/pnpm/blob/main/autofyn_audit/exploits/vuln11_git_upload_pack_rce/exploit.sh)",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "pnpm"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "10.34.0"
32+
}
33+
]
34+
}
35+
]
36+
},
37+
{
38+
"package": {
39+
"ecosystem": "npm",
40+
"name": "pnpm"
41+
},
42+
"ranges": [
43+
{
44+
"type": "ECOSYSTEM",
45+
"events": [
46+
{
47+
"introduced": "11.0.0"
48+
},
49+
{
50+
"fixed": "11.4.0"
51+
}
52+
]
53+
}
54+
]
55+
}
56+
],
57+
"references": [
58+
{
59+
"type": "WEB",
60+
"url": "https://github.com/pnpm/pnpm/security/advisories/GHSA-p4xf-rf54-rj3x"
61+
},
62+
{
63+
"type": "ADVISORY",
64+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-50014"
65+
},
66+
{
67+
"type": "PACKAGE",
68+
"url": "https://github.com/pnpm/pnpm"
69+
}
70+
],
71+
"database_specific": {
72+
"cwe_ids": [
73+
"CWE-88"
74+
],
75+
"severity": "MODERATE",
76+
"github_reviewed": true,
77+
"github_reviewed_at": "2026-06-26T22:53:21Z",
78+
"nvd_published_at": "2026-06-25T18:16:39Z"
79+
}
80+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-q6j5-fjx5-2mc3",
4+
"modified": "2026-06-26T22:53:01Z",
5+
"published": "2026-06-26T22:53:01Z",
6+
"aliases": [
7+
"CVE-2026-50021"
8+
],
9+
"summary": "pnpm Has an Integrity Check Bypass via Missing Lockfile Integrity Field",
10+
"details": "## Summary\n\npnpm's tarball extraction worker skips integrity verification when the `integrity` field is absent from the lockfile resolution. If an attacker can both modify `pnpm-lock.yaml` to remove the `integrity:` field and cause the referenced registry URL to serve altered package content, `pnpm install --frozen-lockfile` can install the altered package without an integrity error. npm's `npm ci` enforces integrity by default; pnpm's behavior of silently skipping verification is a pnpm-specific fail-open gap.\n\n## Vulnerability Details\n\nThe `addTarballToStore` function in `worker/src/start.ts` (lines 189-204) checks `if (integrity)` before verifying the tarball hash. The `TarballResolution` type declares `integrity` as optional (`integrity?: string`). When the lockfile omits the `integrity` field, the guard evaluates to `false`, skipping hash verification entirely. The worker then computes a new hash from the unverified content and stores it as legitimate.\n\n```typescript\n// worker/src/start.ts:189-204\nfunction addTarballToStore ({ buffer, storeDir, integrity, ... }: TarballExtractMessage) {\n if (integrity) { // false when integrity is undefined\n const { algorithm, hexDigest } = parseIntegrity(integrity)\n const calculatedHash = crypto.hash(algorithm, buffer, 'hex')\n if (calculatedHash !== hexDigest) {\n return { status: 'error', error: { type: 'integrity_validation_failed', ... } }\n }\n }\n return {\n status: 'success',\n value: { integrity: integrity ?? calcIntegrity(buffer) },\n }\n}\n```\n\n## Proof of Concept\n\n```bash\nbash autofyn_audit/exploits/vuln1_integrity_bypass/exploit.sh\n# Publishes a package, generates lockfile, republishes tampered version,\n# strips integrity field, re-runs install --frozen-lockfile.\n# Result: PASS -- tampered package installed without integrity error.\n```\n\n## Impact\n\nSupply chain compromise in environments where an attacker can both alter the lockfile and cause the referenced registry URL to serve altered package content. The `--frozen-lockfile` flag does not fail closed when the integrity field is missing.\n\n## Suggested Remediation\n\nRequire an `integrity` field for remote tarball resolutions. Change the `if (integrity)` guard to fail when integrity is absent for non-local packages. When `--frozen-lockfile` is active, reject lockfile entries that lack integrity for remote packages.\n\n---\n\n> Discovered by [AutoFyn](https://github.com/SignalPilot-Labs/AutoFyn)\n> Full audit report: [audit_report.md](https://github.com/tempcollab/pnpm/blob/main/autofyn_audit/audit_report.md)\n> Exploit script: [exploit.sh](https://github.com/tempcollab/pnpm/blob/main/autofyn_audit/exploits/vuln1_integrity_bypass/exploit.sh)",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "pnpm"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "11.0.0"
29+
},
30+
{
31+
"fixed": "11.4.0"
32+
}
33+
]
34+
}
35+
]
36+
},
37+
{
38+
"package": {
39+
"ecosystem": "npm",
40+
"name": "pnpm"
41+
},
42+
"ranges": [
43+
{
44+
"type": "ECOSYSTEM",
45+
"events": [
46+
{
47+
"introduced": "0"
48+
},
49+
{
50+
"fixed": "10.34.1"
51+
}
52+
]
53+
}
54+
]
55+
}
56+
],
57+
"references": [
58+
{
59+
"type": "WEB",
60+
"url": "https://github.com/pnpm/pnpm/security/advisories/GHSA-q6j5-fjx5-2mc3"
61+
},
62+
{
63+
"type": "ADVISORY",
64+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-50021"
65+
},
66+
{
67+
"type": "PACKAGE",
68+
"url": "https://github.com/pnpm/pnpm"
69+
}
70+
],
71+
"database_specific": {
72+
"cwe_ids": [
73+
"CWE-354"
74+
],
75+
"severity": "MODERATE",
76+
"github_reviewed": true,
77+
"github_reviewed_at": "2026-06-26T22:53:01Z",
78+
"nvd_published_at": "2026-06-25T18:16:39Z"
79+
}
80+
}

0 commit comments

Comments
 (0)