Skip to content

fix(sbom): resolve pnpm dependency edges from the lockfile graph #1107

Description

@sonukapoor

Note: this is an in-house item already being handled by the maintainer - not open for contribution. Filed for tracking only.

Follow-up to #1079, which fixed SBOM dependency-graph completeness for npm only.

Problem

npm SBOM output now resolves dependency edges from the lock graph, which is complete. pnpm still derives edges by prefix-matching PackageRef.paths, and those are capped at MAX_PATHS_PER_PACKAGE = 5 (src/parsers/pnpm-lock.ts:8). A package reachable by more than five routes loses edges, and one whose five surviving routes all traverse packages excluded from the scan ends up with no parent edge at all.

For scale, measured on the npm side before the fix: 592 of 2549 packages exceeded five paths, and 5 percent of packages ended up with no parent. There is no reason to expect pnpm to be better.

Dependency relationships are one of the seven NTIA minimum elements, so this weakens the SBOM for exactly the compliance use case it serves. website/docs/spdx.md currently documents the limitation honestly, split by package manager.

Why this should be the cheapest of the three

src/parsers/pnpm-lock.ts already builds a complete key-to-children map at lines 108 and 124 (and again at 164/189 for the second lockfile shape):

const graph = new Map<string, string[]>();
...
graph.set(ref.key, [...depKeys]);

That map is populated before any capping. The work is mapping those keys to name@version, inverting to child-to-parents, and exposing it in the shape resolveDependencyEdges already consumes.

Important: do not record edges during the path traversal

The BFS at pnpm-lock.ts:278-300 prunes once a package reaches five paths:

if ((pkg?.paths?.length ?? 0) >= MAX_PATHS_PER_PACKAGE && !(pkg?.paths ?? []).some(...)) continue;

Recording edges inside that walk would inherit the same incompleteness. This needs a separate edge-only pass, which is why the npm equivalent is cheap: it records edges and never materialises paths.

Scope

  • Expose a child-to-parents edge map from the pnpm parser, keyed by name@version.
  • Extend resolveDependencyEdges (src/output/sbom-dependency-edges.ts) to use it when the scan source is pnpm-lock, keeping the empty-list fallback for the ecosystems not yet covered.
  • Handle the same two cases npm needed: workspace members, which are linked rather than installed and carry no version, and parents filtered out of the scanned set. Both anchor to the root rather than orphaning the package. Never emit a reference to a package absent from the document.
  • Update website/docs/spdx.md, which currently lists pnpm among the ecosystems with the limitation.

Keep MAX_PATHS_PER_PACKAGE at 5. It earns its place in remediation output, where a representative route is enough. This is about decoupling two use cases that currently share one bound, not raising it.

Verification

examples/astro is a large real pnpm monorepo. Before the fix it produces 3513 path-derived edges; count how many packages have no parent edge, then confirm that drops to the root project alone, and that there are zero dangling references.

Estimated half a day. Do this one first: it is the cheapest, and pnpm is where the positioning is strongest since pnpm users have no npm audit equivalent.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingin-houseMaintainer-handled internal work - not open for contribution

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions