Evidence & Evaluation 6 October 2026 7 min read 1,628 words

Nothing to check still passes

Three of the security notes in pnpm 12.10.0 describe the same defect in different vocabularies: a verifier whose passing state included the case where there was nothing to verify. The format those verifiers read did not change, and neither did the tools that audit builds for everyone else.

The argument

pnpm has spent two releases teaching its installer to refuse what the lockfile does not say, because an absent value there has always satisfied the check reading it — and the scanners the industry audits builds with still read that silence as a clean result.

Three of the security notes in pnpm 12.10.0, released on 6 October, describe the same defect in three vocabularies. Config dependencies are now verified against their registry before they are installed, and a lockfile can no longer replace the integrity of one pinned in the version+integrity form. Lockfile verification now checks the tarballs inside a variations resolution against the registry, and an entry whose variations resolution is empty is rejected outright. And pnpm audit signatures now verifies signatures against the integrity recorded in the lockfile, because — the note is admirably direct about this — "packages without a recorded integrity cannot pass signature verification." They could before.

Read that last one slowly. A signature check whose passing state included the packages for which no signature evidence had been recorded. Not a check that failed open under load, or that could be fooled by a crafted archive. A check that returned verified for the case where there was nothing to verify.

None of the three is a parsing bug, and none of them is interesting as a vulnerability; they are patch notes in a point release, found and closed by a project that is unusually careful about this part of its surface. What makes them worth an architect's half hour is that they are the same bug three times, in three unrelated verifiers inside one codebase, in one release. In each, the lockfile left something out, and the code that read it treated the omission as a satisfied condition.

That pattern is not pnpm's. It belongs to the artefact. A lockfile is the one file in a modern build that an engineer is supposed to be able to read instead of trust: committed, diffed in review, pinned by hash, the thing you point at when somebody asks what shipped. Its entire promise is that the install is a function of the file rather than of the registry's mood on a Tuesday. And the way that file says nothing is here is by not saying anything — which is also the way it says nothing is wrong.

The shape of the file makes this concrete. Since pnpm 11, pnpm-lock.yaml has not reliably been one YAML document. It is a stream of up to two: a leading env document that records the package-manager bootstrap — the config dependencies and the pnpm version resolved for the project — and then the project's own dependency graph. pnpm's own splitter, a small Rust module whose header comment says plainly that "pacquet only consumes the second document," works on text rather than on a parse tree. A file carries an env document only if it begins with ---\n; the env document runs to the next \n---\n; what follows is the main one.

What follows, when there is no separator, is the empty string. The comment explains why that is correct: "a leading ---\n with no following separator is an env-only file: a lockfile with no dependencies whose empty main document was trimmed off together with the separator." So a project with no dependencies has a lockfile that contains exactly one document, and pnpm infers no dependencies from a document that is not there. One more guard keeps that inference honest — the file qualifies as env-only only if it contains the literal byte sequence "\n .:\n configDependencies:", an indentation-sensitive substring match, adopted for a reason stated right beside it: the env document is a fraction of a percent of a lockfile, and reading a multi-megabyte graph to slice three kilobytes off its front would make every command in the repository pay for data it never looks at.

Four days before 12.10.0, pnpm 12.9.0 made that shape a legitimate input to a hardened install. Among its fixes: pnpm install --frozen-lockfile "also succeeds when pnpm-lock.yaml records only the pinned pnpm version, as other commands write it when they run before the first install." Reasonable, and plainly correct for the case it addresses. It also means that the file shape in which the artefact states nothing at all now passes the gate whose name means do not accept anything the lockfile does not already contain.

pnpm knows exactly where this leads, and says so in its documentation rather than leaving it to be discovered. A tool that reads only the first document of a two-document lockfile, the lockfile reference warns, "does not fail loudly. The env document is a structurally valid lockfile — it just describes an importer with no dependencies. Such a tool reports that the project has no dependencies, and therefore no vulnerabilities, and a CI gate built on it passes." Then the detail that should bother anyone who has ever accepted a scan as evidence: because the env document does contain packages, "a broken reader emits a plausible-looking result listing pnpm's own binaries." Hence the instruction to scanner authors, which is really an instruction about epistemology: verify your scanner against a two-document lockfile, because "'it reported something' is not a sufficient check here."

The three scanners most likely to be the thing standing between an organisation and an unexamined dependency have each now been taught to read the stream, between June and August of this year, in three separate commits that arrived at three different answers to a question the file does not answer: what counts as installed.

Anchore's syft catalogs every document, and the comment above the change is the most lucid sentence anyone has written about this class of failure: "Reading only the first document yields a well-formed SBOM that contains pnpm's own release binaries and none of the project's dependencies, with nothing to signal that it is wrong." So syft reports pnpm's own release binaries as components, deliberately, on the ground that they are really installed on disk and the alternative "would drop them from the SBOM entirely." Google's OSV-SCALIBR decodes every document and unions what it finds, reaching the same place by a shorter route. Aqua's trivy goes the other way: it decodes the documents in order, skips any whose importers carry configDependencies or packageManagerDependencies — "it holds the package manager environment, not the project dependencies, so it must be skipped" — and returns the first document that survives. Config dependencies are real npm packages, installed into node_modules/.pnpm-config; pnpm's guidance to vulnerability scanners is to read every document precisely so they are not missed. Trivy reports the project's graph without them. Hand it an env-only lockfile and it writes one line at debug level — "No project lockfile document found" — and returns no packages and no error.

So two reputable scanners, pointed at one repository, now produce different inventories of the same build, and neither is wrong by the file's own lights, because the file does not say. One of them, on the shape that pnpm started accepting in CI last week, returns nothing and reports success.

The obvious objection is that this is an ordinary bug class being competently retired. Formats change; readers lag; everyone catches up. The fail-open cases pnpm closed on 6 October were narrow, the project documented the hazard before most scanner maintainers had met it, and the three tools fixed themselves within ten weeks of each other without anybody filing a CVE. Demanding that every machine-generated artefact distinguish deliberately empty from not recorded buys you schema ceremony, migrations, and a version field for every absence — a real cost, paid on every build, against a risk that materialises rarely. There is a decent engineering case for leaving absence cheap and fixing readers as they break.

What that case misses is where the fixes went. All of them landed in readers: three verifiers in pnpm's installer, one cataloger each in syft, trivy and OSV-SCALIBR. The format's semantics did not change, which means the old semantics are still in force for every program nobody has got to yet — the in-house script that diffs lockfiles before release, the compliance pipeline that counts components, the dashboard that has been reporting zero criticals for a quarter. Each of those will be fixed, if it is fixed, by somebody independently rediscovering that the file can decline to mention things. And a risk that materialises rarely is the wrong thing to be relaxed about: a check that passes when there is nothing to check is most dangerous exactly where it is rare, because that is the path with no test covering it and no operator who has ever seen it fail.

The general form is worth naming, because it outlives pnpm and npm and lockfiles. When we decide which artefacts count as evidence about a system, we almost always reason about provenance: it was generated by the tool, it is committed, it was reviewed, it is signed. We do not ask whether the artefact is capable of distinguishing there is nothing to report from I have nothing to report with. Those are opposite facts about a build, and a surprising number of our instruments render them as the same bytes — then hand the result to a gate that was only ever designed to tell pass from fail.

The lockfile earned its authority honestly, by being the one file in the build a person could open and understand. It is still that. What it cannot do, and never could, is say I am empty on purpose in a way that a reader is obliged to notice. Until last week, pnpm's own installer did not notice either. The next reader to be caught by it will almost certainly find out the same way the last three did: not from a failure, but from someone who went looking for something they expected to be there.

What this is argued from

Reporting and primary material the piece rests on, dated at the time of writing. The interpretation is mine; the facts belong to these.

  1. pnpm 12.10.0 release notes pnpm · 2026-10-06
  2. pnpm 12.9.0 release notes pnpm · 2026-10-02
  3. Reading pnpm-lock.yaml pnpm · 2026-09-04
  4. Mitigating supply chain attacks pnpm · 2026-09-29
  5. Multi-document YAML helpers for pnpm-lock.yaml pnpm · 2026-10-06
  6. Trivy pnpm lockfile parser Aqua Security · 2026-10-06
  7. Syft pnpm lockfile cataloger Anchore · 2026-10-06
  8. OSV-SCALIBR pnpm lockfile extractor Google · 2026-10-06

Editorials on this site are written to be argued with. If you think the reading is wrong, it probably is in some particular way, and that is the useful part.

lockfilessupply chainsbomverificationpackage managers