fix(sbom): resolve Yarn dependency edges from the lock graph - #1132
Merged
Merged
Conversation
sonukapoor
force-pushed
the
bugfix/issue-1109-yarn-sbom-edges
branch
from
September 14, 2026 12:35
bb476b4 to
be09309
Compare
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
force-pushed
the
bugfix/issue-1109-yarn-sbom-edges
branch
from
September 14, 2026 12:57
be09309 to
e8500d9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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: onexamples/twenty, 4239 of 5452 packages had no parent edge at all, and onexamples/storybook2612 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.tsexposes the complete graph behind the same three-method shape npm, pnpm and Bun already present, soresolveDependencyEdgesgains 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.0anda@~1.2.0can both resolve toa@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 alreadyname@version, so unlike pnpm and Bun one name and version is one node, andnodeIdsForreturns at most one id.isYarnBerry,parseBerryDependenciesandparseYarnPackageKeyare now exported so the graph module reuses the parser's primitives rather than reimplementing them. No new dependency: the classic path uses the sameyarn-lockfileparser the loader already uses.Measured
All with zero dangling references, and the only parentless package being the root project:
DEPENDENCY_OFbeforeexamples/twentyexamples/storybookexamples/cal-comBoth 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.tsnever callsresolveDependencyEdgesand emits nodependenciesarray at all, so CycloneDX output carries no dependency relationships regardless of package manager.cyclonedx.mddoes 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/gatsbystill shows 380 packages with no parent, and it is not a Yarn problem. Itspackage.jsonhas nonamefield, so no root package is emitted,rootIdis falsy, andsrc/output/spdx.ts:327silently drops everyparent: nulledge. That affects npm, pnpm and Bun identically. Documented as a caveat inspdx.mdhere; the real fix is to synthesise a root when the name is missing, which I will file separately.Closes #1109