The recurring npm attack pattern is boring by now: a legitimate package publishes a malicious version, projects install it, and an install-time lifecycle script steals tokens and CI secrets before anyone reads a line of code. The 2026 worm campaigns ran on exactly this loop.
pnpm v11 ships real defenses against it. Build scripts do not run without approval. A release-age window keeps brand-new versions out until they have survived in the wild for a while. Exotic transitive sources, git URLs and tarballs where a registry package should be, get blocked. This is the strongest default posture any mainstream package manager ships today.
All of it is configured in pnpm-workspace.yaml. Which lives in the repository. Which means one committed line weakens the defense for every developer and every CI runner that clones the project:
packages:
- 'packages/*'
dangerouslyAllowAllBuilds: true # every dependency now runs install scripts
The setting name is honest about it. Plenty of repos ship it anyway, because a dependency's build step failed once and the quick fix stuck.
The 9 things worth flagging
Aguara's new pnpm-policy analyzer reads pnpm-workspace.yaml and flags settings weakened below the pnpm v11 defaults. The full set, with the reasoning:
| Rule | Severity | What it means |
|---|---|---|
PNPM_DANGEROUS_BUILDS_001 | HIGH | dangerouslyAllowAllBuilds: true: every dependency runs install-time lifecycle scripts without approval. The exact entry point of the worm pattern. |
PNPM_STRICT_DEP_BUILDS_DISABLED_001 | MEDIUM | strictDepBuilds: false downgrades unapproved build scripts from a hard failure to a warning nobody reads. |
PNPM_EXOTIC_SUBDEPS_DISABLED_001 | MEDIUM | blockExoticSubdeps: false lets transitive dependencies resolve from git and tarball URLs instead of the registry. |
PNPM_TRUST_LOCKFILE_001 | MEDIUM | trustLockfile: true skips supply-chain verification for entries already in the lockfile. |
PNPM_BUILD_APPROVAL_PENDING_001 | MEDIUM | An allowBuilds entry left undecided: a build script is still pending review, and the decision will probably be made under deadline pressure. |
PNPM_MIN_RELEASE_AGE_DISABLED_001 | LOW | The release-age window is disabled: brand-new versions install immediately. |
PNPM_MIN_RELEASE_AGE_NON_STRICT_001 | LOW | The window exists but is not strictly enforced. |
PNPM_TRUST_POLICY_OFF_001 | LOW | trustPolicy: off set explicitly. |
PNPM_LEGACY_BUILD_POLICY_001 | INFO | pnpm v10 build settings that v11 silently ignores. You think you have a policy; pnpm disagrees. |
A missing setting never fires. pnpm's v11 defaults are good; the analyzer only speaks up when a repo opts out of them.
The kebab-case trap
The most useful thing we learned building this came from testing against real pnpm versions instead of trusting documentation. In pnpm-workspace.yaml, pnpm 11 only honors camelCase keys. Write min-release-age instead of minReleaseAge and pnpm silently ignores the line. No warning, no error: the kebab-case spelling belongs to .npmrc, not to the workspace file.
That cuts both ways. A kebab-case hardening line gives you nothing but the feeling of safety. And a kebab-case weakening line is equally inert, which is why Aguara only matches the exact keys pnpm honors: flagging dangerously-allow-all-builds: true would be a false positive, because pnpm never reads it. We verified this against pnpm 8, 10, and 11.5.2; notably, pnpm 10 echoes config keys it does not implement, which is exactly how a wrong assumption survives a casual check.
The lockfile side of the same story
Posture is one half; the lockfile is the other. npm-family lockfiles support aliases, so a dependency can be installed under a local name that points at a different registry package: safe-ipc@npm:node-ipc@9.2.3. A scanner that matches advisories against the local name misses the compromised package behind the rename.
Aguara resolves npm: aliases to the real registry package in pnpm-lock.yaml, and the same resolution is rolling out across the rest of the npm lockfile family. For pnpm specifically this is hardening rather than a gap: pnpm itself normalizes aliased installs to real-name lockfile keys, so the protection matters for hand-edited or poisoned lockfiles and historical shapes.
Audit it in one command
$ aguara audit . --ci
HIGH pnpm-workspace.yaml:3 PNPM_DANGEROUS_BUILDS_001
dangerouslyAllowAllBuilds: true lets every dependency
run install-time lifecycle scripts without approval
audit: exit 1
Each finding points at the exact line and resolves with aguara explain <ID>. The check runs offline, before install, in CI or on a fresh clone.
Is your pnpm posture what you think it is?
One scan covers the workspace policy, the lockfile, and everything else the repo ships.