Skip to content

v11 release tarballs contain broken absolute symlinks pointing at the GitHub Actions runner filesystem #11398

@comp615

Description

@comp615

Self-checks

  • I have searched the issue tracker for existing reports — none mention verbatimSymlinks, absolute symlinks, or /home/runner in tarballs
  • I am running the latest stable version (verified against v11.0.0 and v11.0.1)

Subject of the issue

The pnpm standalone-executable release tarballs published to GitHub Releases (pnpm-{darwin,linux}-{x64,arm64}.tar.gz for v11.0.0 and v11.0.1) contain four broken absolute symlinks under dist/node_modules/.bin/ whose targets point at the GitHub Actions runner's filesystem path, not anywhere on the user's machine:

$ curl -sL https://github.com/pnpm/pnpm/releases/download/v11.0.1/pnpm-darwin-arm64.tar.gz | tar -tvz | grep '^l'
lrwxrwxrwx  0 runner 1001  0 Apr 29 17:07 dist/node_modules/.bin/node-gyp    -> /home/runner/work/pnpm/pnpm/pnpm/dist/node_modules/node-gyp/bin/node-gyp.js
lrwxrwxrwx  0 runner 1001  0 Apr 29 17:07 dist/node_modules/.bin/node-which  -> /home/runner/work/pnpm/pnpm/pnpm/dist/node_modules/which/bin/which.js
lrwxrwxrwx  0 runner 1001  0 Apr 29 17:07 dist/node_modules/.bin/nopt        -> /home/runner/work/pnpm/pnpm/pnpm/dist/node_modules/nopt/bin/nopt.js
lrwxrwxrwx  0 runner 1001  0 Apr 29 17:07 dist/node_modules/.bin/semver      -> /home/runner/work/pnpm/pnpm/pnpm/dist/node_modules/semver/bin/semver.js

Lenient extractors (system tar, install.sh, npm i -g @pnpm/exe) silently extract these as dangling symlinks; nothing at runtime ever traverses them, so the bug is invisible during normal use. Strict extractors that perform path-traversal validation (e.g. hermit) refuse to extract, breaking installation:

fatal:hermit: .../dist/node_modules/.bin/node-gyp: illegal symlink target
"/home/runner/work/pnpm/pnpm/pnpm/dist/node_modules/node-gyp/bin/node-gyp.js"
(resolves to /home/runner/work/... which is outside ...)

Expected behaviour

The symlinks under dist/node_modules/.bin/ should be relative (e.g. ../node-gyp/bin/node-gyp.js), so they remain valid at any extraction location and don't leak the build host's filesystem layout.

Actual behaviour

They are absolute paths into the GitHub Actions runner's working directory (/home/runner/work/pnpm/pnpm/pnpm/...), which:

  1. Are dangling on every user's machine (a supply-chain smell)
  2. Cause strict tarball extractors to refuse the archive
  3. Leak build-host paths into a public artifact

Root cause

pnpm/artifacts/exe/scripts/build-artifacts.ts#L48:

fs.cpSync(distSrc, distDest, { recursive: true })

Node's fs.cpSync defaults to verbatimSymlinks: false, which resolves relative symlinks into absolute paths at the source filesystem location during the copy. This is a well-known behaviour, see nodejs/node#41693 and the Node.js docs:

verbatimSymlinks <boolean> When true, path resolution for symlinks will be skipped. Default: false.

So a relative symlink in the source pnpm checkout (dist/node_modules/.bin/node-gyp -> ../node-gyp/bin/node-gyp.js) becomes absolute (-> /home/runner/work/pnpm/pnpm/pnpm/dist/node_modules/node-gyp/bin/node-gyp.js) in the destination directory that gets archived.

Steps to reproduce

curl -sL https://github.com/pnpm/pnpm/releases/download/v11.0.1/pnpm-darwin-arm64.tar.gz \
  | tar -tvz | grep '^l'

(Same result for pnpm-darwin-x64, pnpm-linux-x64, pnpm-linux-arm64 archives in both v11.0.0 and v11.0.1.)

Spot-check a v10.x release for comparison: v10.x ships bare binaries (no tarball, no dist/ directory in the artifact), so this regression is specific to v11+.

Suggested fix

One-line change in pnpm/artifacts/exe/scripts/build-artifacts.ts:

- fs.cpSync(distSrc, distDest, { recursive: true })
+ fs.cpSync(distSrc, distDest, { recursive: true, verbatimSymlinks: true })

Would also recommend adding a regression check to the release workflow that walks the packaged dist/ and fails if any symlink target is absolute or resolves outside the archive root.

I'd be happy to send a PR if that's useful.

Environment

  • pnpm version: 11.0.0, 11.0.1 (release tarballs)
  • Node.js: N/A (issue is in build artifacts, not runtime)
  • Tested extraction with: hermit (rejects), system tar (extracts as dangling)

🤖 Issue drafted with Amp assistance after reproducing the failure mode and tracing the root cause.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions