Skip to content
loke.dev
Two stacked lockfile sheets pass through one scanner; the upper dependency graph is lit while the lower graph remains in shadow

Fix pnpm 12 Security Scanners Missing Project Dependencies

Detect pnpm 12 multi-document lockfiles, verify security scanners and SBOMs, and avoid false clean results during the release-candidate upgrade.

Published Updated 7 min read

pnpm 12.0.0-rc.3 can write pnpm-lock.yaml as two YAML documents. The first describes pnpm itself and any config dependencies. The second contains your application's dependency graph. A scanner that reads only the first document can return a clean result while missing every project dependency.

This is not a pnpm audit failure. In a local fixture with a deliberately vulnerable minimist version, pnpm audit found the same two advisories in both lockfile shapes. The risk sits at the boundary between pnpm's valid multi-document file and external tools that still assume one YAML document.

The safe response

  • Do not accept a zero-dependency graph, empty vulnerability report, or non-empty SBOM as proof that the pnpm 12 migration is safe.
  • Check whether pnpm-lock.yaml begins with a YAML document marker.
  • Run the exact security and SBOM tools used by CI against a known fixture before merging the package-manager upgrade.
  • Stay on pnpm 11 if a required consumer still reads the wrong document.
  • Use pmOnFail: ignore only when an external tool already enforces the pnpm version and the repository does not use config dependencies.

As of August 12, 2026, 12.0.0-rc.3 is the current pnpm 12 release candidate. The scanner reports discussed below are still open. Treat every version and tool result in this guide as time-scoped.

Confirm the lockfile shape

pnpm's config dependency documentation says integrity data for config dependencies is stored in a dedicated env lockfile document. pnpm 12 also records its resolved package-manager version there by default, so an ordinary project with a packageManager field can get the two-document form.

head -n 1 pnpm-lock.yaml
rg -n '^---$' pnpm-lock.yaml

A pnpm lockfile that starts with --- has an env document first and the project document last. On the verified rc.3 fixture, the markers and relevant importer keys appeared like this:

1:---
8:    packageManagerDependencies:
101:---
111:    dependencies:

Do not delete or reorder either document by hand. pnpm owns the file and can regenerate it. The durable fix is for every consumer to parse the YAML stream correctly.

Add a temporary CI guard

Use this guard while scanner compatibility is still being audited. It detects the leading marker used by pnpm for the two-document form and blocks the upgrade before an unverified security job can fail green.

import { readFile } from "node:fs/promises";

const path = process.argv[2] ?? "pnpm-lock.yaml";
const lockfile = await readFile(path, "utf8");

if (lockfile.startsWith("---\n") || lockfile.startsWith("---\r\n")) {
  console.error(
    `Multi-document pnpm lockfile detected: ${path}. Verify every dependency scanner before continuing.`,
  );
  process.exit(1);
}

console.log(`Single-document pnpm lockfile: ${path}`);
{
  "scripts": {
    "check:pnpm-lockfile": "node check-pnpm-lockfile-shape.mjs"
  }
}

I ran this exact guard against both local fixtures. It exited 1 for the default rc.3 lockfile and 0 for the single-document control created with pmOnFail: ignore.

default rc.3 fixture:
Multi-document pnpm lockfile detected
exit 1

pmOnFail: ignore fixture:
Single-document pnpm lockfile
exit 0

This guard is intentionally conservative. It does not say multi-document YAML is broken. It says your pipeline must prove that each consumer supports it before proceeding.

Why a green scanner can be wrong

The open OSV-Scanner extractor report reproduces a two-document pnpm lockfile where OSV-Scanner 2.5.0 finds pnpm's own packages, reports no vulnerabilities, and exits 0 while the project document contains vulnerable minimist 1.2.0.

The separate Syft report shows the same parsing mistake in SBOM output. Syft 1.51.0 catalogs pnpm and its platform binaries from the env document, but not the project's dependency.

That second failure is easy to miss because the SBOM is not empty. A count greater than zero is a weak assertion when every listed package belongs to the package manager. Validate at least one direct application dependency by name and version.

# Replace these commands with your pipeline's actual output format.
# The assertion is the important part: a known direct dependency must exist.
syft scan file:pnpm-lock.yaml -o json > sbom.json
jq -e '.artifacts[] | select(.name == "@acme/server")' sbom.json

The package name above is illustrative. Use a stable direct dependency from your own repository and keep the assertion beside the scanner version pin.

Check GitHub's dependency graph separately

GitHub's dependency graph documentation says the graph is built by parsing repository manifests and lockfiles. It feeds dependency review and known-vulnerability information, so a sudden drop to zero dependencies is not a cosmetic dashboard change.

The pnpm report that surfaced this issue describes a repository where the dependency graph showed zero dependencies after the pnpm 12 bump and existing alerts closed without the vulnerable versions changing. That is one reported repository, not proof that every GitHub repository will behave identically.

After changing the package-manager version, compare the dependency count and several known direct and transitive packages. If the graph collapses or alerts disappear without a matching lockfile update, stop the rollout and preserve the before-and-after evidence.

Know which tools are affected

The same fixture can produce different answers because each tool owns its pnpm parser.

  • pnpm audit: verified locally on rc.3; it found both minimist advisories in one-document and two-document fixtures.
  • OSV-Scanner 2.5.0: the open upstream report shows a false clean result on the two-document fixture.
  • Syft 1.51.0: the open upstream report shows an SBOM containing pnpm binaries instead of project dependencies.
  • GitHub dependency graph: one pnpm issue reports zero dependencies and closed alerts after the migration.

Do not generalize these results to newer releases. Pin scanner versions in CI, read their release notes, and rerun the fixture when upgrading them.

Choose the least disruptive workaround

Stay on pnpm 11

This is the safest choice when the pnpm 12 migration is optional and a required scanner has not shipped support. pnpm 12 is still a release candidate, so there is little value in weakening a security pipeline just to complete the upgrade early.

Use a scanner that parses both documents

A tool can be safe for this case if it reads the YAML stream and includes the project document. For vulnerability or SBOM work, parsing only the last document may still omit config dependencies, which are real packages. A correct security inventory should account for both documents rather than replacing one with the other.

Test the version you actually run. A vendor statement about pnpm support is not enough if it does not cover the two-document shape.

Use pmOnFail: ignore only with external version management

pnpm documents pmOnFail: ignore as skipping the packageManager version check, and recommends it when another tool such as asdf, mise, or Volta manages that version.

pmOnFail: ignore

In the local rc.3 control, that setting removed the env document and restored a single-document lockfile. It is not a free formatting switch. pnpm stops enforcing and downloading the package-manager version, so CI must pin it somewhere else.

This workaround also does not remove an env document required by config dependencies. If the workspace uses configDependencies, keep the multi-document lockfile and fix or replace the consumer.

Verify pnpm audit without treating it as an SBOM

The local control used minimist 1.2.0 only to prove that both lockfile shapes still expose the project dependency to pnpm audit.

{
  "private": true,
  "packageManager": "pnpm@12.0.0-rc.3",
  "dependencies": {
    "minimist": "1.2.0"
  }
}
pnpm install --lockfile-only
pnpm audit --json

Both fixtures exited 1 and reported GHSA-vh95-rmgr-6w4m and GHSA-xvch-5gv4-984h. Do not add an intentionally vulnerable package to a real repository. Use an isolated fixture and delete it after the compatibility test.

pnpm audit is a useful cross-check for this pnpm-specific parsing question. It does not generate the SBOM your organization may require, and it does not prove that GitHub, OSV-Scanner, Syft, or another consumer parsed the same file correctly.

A rollout checklist

  • Record the current pnpm version, scanner versions, dependency count, and several known package paths.
  • Generate pnpm-lock.yaml with the exact pnpm 12 release candidate under review.
  • Run the lockfile-shape guard.
  • Test each scanner against an isolated fixture with one known vulnerable dependency.
  • Inspect the SBOM for a known project package, not only a nonzero component count.
  • Compare GitHub's dependency graph and alert state after the lockfile reaches the default branch.
  • Keep pnpm 11 or externalize version enforcement if any required consumer still reads the wrong document.
  • Remove the temporary guard only after every pinned consumer version passes the same fixture.

There is already broad demand for pnpm 11 and 12 support in Dependabot's open pnpm support issue. That does not guarantee multi-document compatibility. Treat the exact lockfile shape as its own acceptance test.

The useful success condition is not that the package-manager upgrade finished. It is that every security consumer still names the dependencies you know are present.