Skip to content

fix(sbom): resolve Yarn dependency edges from the lock graph - #1132

Merged
sonukapoor merged 3 commits into
mainfrom
bugfix/issue-1109-yarn-sbom-edges
Sep 15, 2026
Merged

sonukapoor merged 3 commits into
mainfrom
bugfix/issue-1109-yarn-sbom-edges

Conversation

@sonukapoor

@sonukapoor sonukapoor commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

Yarn SBOM edges were rebuilt from PackageRef.paths, which are capped at five paths of ten segments per package. This cost far more on Yarn than on any other ecosystem: on examples/twenty, 4239 of 5452 packages had no parent edge at all, and on examples/storybook 2612 of 3009. Dependency relationships are an NTIA minimum element, so the document was close to useless for the compliance case it exists to serve.

The issue filed this as the largest and riskiest of the three follow-ups, to be done last. The risk assessment was right but the priority was backwards: Yarn had by far the worst graph of the four.

New yarn-lock-graph.ts exposes the complete graph behind the same three-method shape npm, pnpm and Bun already present, so resolveDependencyEdges gains one dispatch branch and the edge building itself is untouched.

Yarn keys lockfile entries by the range that requested them rather than by the version installed, so a@^1.0.0 and a@~1.2.0 can both resolve to a@1.2.3. Resolving a declared dependency to the entry that satisfies it goes through a selector-to-key map, which both lockfile formats build the same way despite being parsed very differently. Canonical keys are already name@version, so unlike pnpm and Bun one name and version is one node, and nodeIdsFor returns at most one id.

isYarnBerry, parseBerryDependencies and parseYarnPackageKey are now exported so the graph module reuses the parser's primitives rather than reimplementing them. No new dependency: the classic path uses the same yarn-lockfile parser the loader already uses.

Measured

All with zero dangling references, and the only parentless package being the root project:

project packages DEPENDENCY_OF before after parentless before after
examples/twenty 5452 1755 13569 4239 1
examples/storybook 3009 519 6493 2612 1
examples/cal-com 3813 - 8346 - 1

Both lockfile formats verified across seven real projects. The classic path resolves every declared dependency: 0 of 10222 unresolved on examples/gatsby.

This completes SPDX dependency-graph resolution for all four supported package managers, alongside #1106 (npm), #1115 (pnpm) and #1125 (Bun).

Scope note, worth being exact about. This series covers SPDX only. src/output/cyclonedx.ts never calls resolveDependencyEdges and emits no dependencies array at all, so CycloneDX output carries no dependency relationships regardless of package manager. cyclonedx.md does not claim otherwise, so nothing documented is wrong, but the gap is real and I will file it separately.

Separate pre-existing bug found while verifying

examples/gatsby still shows 380 packages with no parent, and it is not a Yarn problem. Its package.json has no name field, so no root package is emitted, rootId is falsy, and src/output/spdx.ts:327 silently drops every parent: null edge. That affects npm, pnpm and Bun identically. Documented as a caveat in spdx.md here; the real fix is to synthesise a root when the name is missing, which I will file separately.

Closes #1109

Yarn SBOM edges were rebuilt from PackageRef.paths, which are capped at
five paths of ten segments per package. This cost more on Yarn than on any
other ecosystem: on examples/twenty, 4239 of 5452 packages had no parent
edge at all, and on examples/storybook 2612 of 3009. Dependency
relationships are an NTIA minimum element, so the document was close to
useless for the compliance case it exists to serve.

New yarn-lock-graph.ts exposes the complete graph behind the same
three-method shape npm, pnpm and Bun already present, so
resolveDependencyEdges gains one dispatch branch and the edge building
itself is untouched.

Yarn keys lockfile entries by the range that requested them rather than by
the version installed, so a@^1.0.0 and a@~1.2.0 can both resolve to
a@1.2.3. Resolving a declared dependency to the entry that satisfies it
goes through a selector-to-key map, which both lockfile formats build the
same way despite being parsed very differently. Canonical keys are already
name@version, so unlike pnpm and Bun one name and version is one node and
nodeIdsFor returns at most one id.

isYarnBerry, parseBerryDependencies and parseYarnPackageKey are now
exported so the graph module reuses the parser's primitives rather than
reimplementing them. No new dependency: the classic path uses the same
yarn-lockfile parser the loader already uses.

Measured, all with zero dangling references and the only parentless
package being the root project:

  examples/twenty     5452 packages, edges 1755 to 13569, orphans 4239 to 1
  examples/storybook  3009 packages, edges  519 to  6493, orphans 2612 to 1
  examples/cal-com    3813 packages, edges         8346, orphans         1

Both formats verified across seven real projects. The classic path
resolves every declared dependency: 0 of 10222 unresolved on gatsby.

This completes dependency-graph resolution for all four supported package
managers.

Closes #1109
Unit tests run against both lockfile formats from one table: every parent
kept for a package reached by two routes, node ids resolving back to name
and version, and a dependency declared by range rather than by version
resolving to the entry that satisfies it.

Integration tests at the edge builder mirror the npm, pnpm and Bun ones.
All four fail without the dispatch branch.
Removes the per-package-manager split now that Yarn is covered, and adds
the one caveat that is not package-manager specific: packages nothing
depends on anchor to the root project, so a package.json with no name
field emits no root and those edges are dropped.
@sonukapoor
sonukapoor force-pushed the bugfix/issue-1109-yarn-sbom-edges branch from be09309 to e8500d9 Compare September 14, 2026 12:57
@sonukapoor
sonukapoor merged commit cea8f64 into main Sep 15, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(sbom): resolve Yarn dependency edges from the lockfile graph

1 participant