Skip to content

pnpm install copies com.apple.quarantine xattr from store blobs to native .node files, causing macOS Gatekeeper to block them #11056

Description

@brettz9

Verify latest release

  • I verified that the issue exists in the latest pnpm release

pnpm version

No response

Which area(s) of pnpm are affected? (leave empty if unsure)

No response

Link to the code that reproduces this issue or a replay of the bug

No response

Reproduction steps

Background / how to reach this state

pnpm's content-addressable store can accumulate com.apple.quarantine xattrs on file blobs when packages are first installed under a Gatekeeper-enabled application (e.g. a terminal inside Sourcetree, a Git client with LSFileQuarantineEnabled=YES). Once quarantine is on a store blob, every subsequent pnpm install — including after deleting node_modules and reinstalling from scratch — propagates the xattr into the project because macOS preserves extended attributes during file copy.

Reproduce from a clean state (manual)

# 1. Install a package containing a native addon
mkdir /tmp/pnpm-quarantine-repro && cd /tmp/pnpm-quarantine-repro
echo '{"name":"repro","private":true,"devDependencies":{"rollup":"4.59.0"}}' > package.json
pnpm install

# 2. Find the native binary in node_modules and the corresponding store blob
BIN=$(find node_modules -name 'rollup.darwin-arm64.node' | head -1)
echo "node_modules binary: $BIN"
STORE_BLOB=$(pnpm store path)/files/2f/$(ls "$(pnpm store path)/files/2f/" | grep "^de" | head -1)
echo "store blob: $STORE_BLOB"

# 3. Check both are clean (no quarantine yet)
xattr -l "$BIN"
xattr -l "$STORE_BLOB"

# 4. Simulate quarantine on the store blob (as macOS would apply it when downloaded
#    via a Gatekeeper-enabled app such as a Git client or browser):
xattr -w com.apple.quarantine "0083;$(printf '%x' $(date +%s));Sourcetree;" "$STORE_BLOB"

# 5. Delete node_modules and reinstall — pnpm pulls from its store
rm -rf node_modules
pnpm install

# 6. Observe: binary in node_modules now has quarantine despite a fresh install
BIN=$(find node_modules -name 'rollup.darwin-arm64.node' | head -1)
xattr -l "$BIN"
# OUTPUT: com.apple.quarantine: 0083;...;Sourcetree;

# 7. Observe: running rollup now triggers Gatekeeper
pnpm exec rollup --version
# Apple dialog: "Apple could not verify rollup.darwin-arm64.node is free of malware"

Describe the Bug

Environment

macOS 26.3.1 arm64 (Darwin 25.3.0)
pnpm 10.32.1
Node 22.16.0

Root cause analysis

  • The native binary rollup.darwin-arm64.node is ad-hoc signed only (no Team ID, not notarized), so Gatekeeper cannot automatically approve it.
  • pnpm copies (not hardlinks across volumes) store file blobs into node_modules, and cp on macOS preserves all xattrs including com.apple.quarantine.
  • After pnpm verifies the file's integrity against the lockfile hash (sha512-L97TFX...), there is no security reason to retain the quarantine xattr.

Expected Behavior

After pnpm writes a file from its store and verifies its integrity hash matches pnpm-lock.yaml, it should strip com.apple.quarantine from the file:

xattr -d com.apple.quarantine <path> 2>/dev/null || true

This is the same strategy Homebrew uses for downloaded artifacts after checksum verification.

Workaround

find node_modules -name '*.node' -exec xattr -d com.apple.quarantine {} \; 2>/dev/null

Which Node.js version are you using?

22.16.0

Which operating systems have you used?

  • macOS
  • Windows
  • Linux

If your OS is a Linux based, which one it is? (Include the version if relevant)

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions