high · 7.5CVE-2026-55697Jun 26, 2026

CVE-2026-55697: pnpm Repository-Controlled configDependencies Native Binary Execution

Rohit Hatagale
AI Security Researcher, SecureLayer7

A malicious repository can place a specially crafted entry in pnpm-workspace.yaml that causes pnpm to download and execute an attacker-controlled native binary whenever a developer or CI runner runs p

Packagepnpm
Ecosystemnpm
Affected< 10.34.2
Fixed in11.5.3

The problem

pnpm supports a configDependencies field in pnpm-workspace.yaml that installs helper packages before the main install runs. Before the fix, if that field declared pacquet or @pnpm/pacquet, pnpm automatically treated the repository-controlled declaration as authorization to delegate the entire install step to the pacquet native binary.

The binary was resolved from node_modules/.pnpm-config and spawned with spawn() under the victim user's full identity, including filesystem access, environment variables, registry credentials, and SSH/git keys. Because the opt-in lived inside the repository itself, any attacker who could commit or push a change to pnpm-workspace.yaml could trigger native code execution on every developer and CI machine that ran pnpm afterward.

Proof of concept

bash
# pnpm-workspace.yaml committed by attacker
packages:
  - .
configDependencies:
  pacquet: 0.2.2

# Attacker-controlled registry publishes "pacquet@0.2.2" with:
# optionalDependencies: { "@pacquet/darwin-arm64": "0.2.2" }
#
# @pacquet/darwin-arm64@0.2.2 ships a "pacquet" binary:
#!/bin/sh
echo "$PWD" > /tmp/pacquet-engine-ran
env > /tmp/pacquet-engine-env
#
# Victim runs:
pnpm install
# pnpm installs configDependencies, detects "pacquet" in configDependencies,
# resolves @pacquet/<platform>-<arch>/pacquet from node_modules/.pnpm-config,
# and spawns it via spawn() -- executing the attacker binary as the victim user.

The root flaw is that installDeps() treated the presence of pacquet or @pnpm/pacquet in configDependencies as implicit authorization to delegate native process execution. No out-of-repo trust signal was required: the repository could grant itself permission.

The patch introduces a new setting, configDependencyInstallEngineAllowlist, readable only from trusted config sources such as global config, CLI flags, or environment variables. The workspace file pnpm-workspace.yaml is explicitly prevented from populating this setting for itself after workspace config is merged.

The patched getPacquetConfigDependencyName() returns undefined when the allowlist is absent, blocking binary selection entirely. Delegation only proceeds when the user has explicitly opted in from outside the repository. This closes CWE-829 (untrusted control sphere), CWE-78 (OS command injection via spawn), and CWE-494 (download of code without integrity check).

The fix

Upgrade to pnpm 10.34.2 (v10 branch) or pnpm 11.5.3 (v11 branch). After upgrading, pacquet delegation requires an explicit opt-in from user-level or global config. Set configDependencyInstallEngineAllowlist to pacquet, @pnpm/pacquet, or * in your global pnpm config (~/.config/pnpm/config.yaml or via pnpm config set) if you intentionally use pacquet as an install engine.

A pnpm-workspace.yaml entry alone is no longer sufficient.

Reporter not attributed.

References: [1][2][3]