Skip to content

fix: include shipped dependencies in JavaScript SBOMs#17

Open
jan-kubica wants to merge 2 commits into
mainfrom
fix/sbom-include-shipped-deps
Open

fix: include shipped dependencies in JavaScript SBOMs#17
jan-kubica wants to merge 2 commits into
mainfrom
fix/sbom-include-shipped-deps

Conversation

@jan-kubica

Copy link
Copy Markdown
Contributor

Problem

The JavaScript SBOM and third-party notices silently omit shipped dependencies. Concretely, a production dependency can be present in package.json, resolved in the lockfile, installed, and shipped — yet absent from provenance/.../sbom.cdx.json and THIRD-PARTY-NOTICES.txt.

Root cause is the cdxgen --required-only flag (src/sbom.rs). For bun lockfiles cdxgen does not resolve the dependency graph (it emits an empty dependencies array) and instead derives component scope from source-usage evidence:

  • a package imported from production source → scope: required
  • everything else → scope: optional

That "everything else" includes genuinely shipped code:

  • transitive runtime dependencies (never imported directly), and
  • production dependencies imported only from .d.ts or test files (e.g. a package used solely for a declare module type augmentation).

--required-only then drops the entire optional set. cdxgen cannot distinguish these from devDependencies — they are byte-for-byte identical in scope/evidence.

Reproduction (real cdxgen 12.1.5, bun, hoisted)

Workspace with a source-imported dep (picocolors) + a production dep imported only via .d.ts/test (debug → transitive ms) + a devDep (left-pad):

install flag result
full --required-only (current) only picocolorsdebug/ms dropped
full none all incl. left-pad (over-reports devDep)
production --required-only only picocolorsstill dropped (prod install alone is not enough)
production none picocolors, debug, ms; left-pad excluded ✅

Only the last combination yields the correct shipped closure, so both changes are required.

Fix

  • Stop passing --required-only so the SBOM reflects the full installed closure.
  • Exclude devDependencies by installing production dependencies only before generation, documented in the README Boundaries section. Consumers' nightly install-command must add --production (paired PRs follow).

Tests

Adds a #[ignore] real-cdxgen integration test mirroring the reproduction (workspace, production install) that asserts debug + transitive ms are present and the left-pad devDependency is absent. It fails if --required-only is reintroduced. CI's integration-real job gains a bun toolchain to run it.

Rollout

This changes generated artifacts: the next provenance release must be cut, and consumer repos must bump provenance-version to it and switch their install to --production (paired PRs). Merging a consumer bump before the release is cut would break its nightly download.

cdxgen was invoked with --required-only. For bun lockfiles cdxgen derives
component scope from source-usage evidence rather than the manifest: a
package is marked `required` only when it has a production source import,
and everything else (shipped transitive dependencies, and production
dependencies imported only from type-declaration or test files) is marked
`optional`. --required-only then drops that whole set, so the generated
SBOM and third-party notices silently under-reported shipped dependencies.

Stop passing --required-only so the SBOM reflects the full installed
closure. Development dependencies are now excluded by installing production
dependencies only before generation (documented in the README Boundaries
section) instead of relying on cdxgen's unreliable bun scope.

Add a real-cdxgen integration test (a workspace with a source-imported
dependency plus a production dependency imported only from a .d.ts and a
test file, installed production-only) asserting the shipped dependency and
its transitive runtime dependency appear while a devDependency does not.
The CI integration-real job gains a bun toolchain to run it.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request removes the --required-only flag from the cdxgen invocation to prevent shipped transitive JavaScript dependencies from being dropped when using bun lockfiles. It also updates the README and adds integration tests to verify this behavior. The reviewer pointed out that omitting --required-only globally causes a regression for non-JavaScript projects (such as Rust), which would now include development dependencies in their SBOMs. The reviewer suggested conditionally applying the --required-only flag only when the JavaScript ecosystem is not present.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/sbom.rs Outdated
Comment on lines +90 to +96
// Deliberately omit cdxgen's --required-only. For bun lockfiles cdxgen
// derives component scope from source-usage evidence rather than the
// manifest, so it marks shipped transitive dependencies (and production
// dependencies imported only from type-declaration or test files) as
// optional and drops them, producing an incomplete SBOM. Completeness of
// the shipped closure is gated on the install instead: callers must
// install production dependencies only (see README "Boundaries").

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Omitting --required-only globally will cause non-JavaScript projects (such as pure Rust projects) to include development dependencies in their generated SBOMs and third-party notices. This is a regression for those ecosystems.\n\nWe should conditionally omit --required-only only when the project contains the Javascript ecosystem, preserving the correct behavior for Rust-only projects.

Suggested change
// Deliberately omit cdxgen's --required-only. For bun lockfiles cdxgen
// derives component scope from source-usage evidence rather than the
// manifest, so it marks shipped transitive dependencies (and production
// dependencies imported only from type-declaration or test files) as
// optional and drops them, producing an incomplete SBOM. Completeness of
// the shipped closure is gated on the install instead: callers must
// install production dependencies only (see README "Boundaries").
// Deliberately omit cdxgen's --required-only for JavaScript projects. For bun lockfiles cdxgen\n // derives component scope from source-usage evidence rather than the\n // manifest, so it marks shipped transitive dependencies (and production\n // dependencies imported only from type-declaration or test files) as\n // optional and drops them, producing an incomplete SBOM. Completeness of\n // the shipped closure is gated on the install instead: callers must\n // install production dependencies only (see README "Boundaries").\n // For other ecosystems (like Rust), we still want to enforce --required-only.\n .args(if !project.ecosystems.contains(&Ecosystem::Javascript) {\n Some(\"--required-only\")\n } else {\n None\n })

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5988c4a294

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sbom.rs Outdated
.arg("--exclude-regex")
.arg(build_exclude_regex(sbom_config))
.arg("--required-only")
// Deliberately omit cdxgen's --required-only. For bun lockfiles cdxgen

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep required-only for non-JavaScript scans

This command is shared by Rust-only and mixed projects, but the new install-time workaround only constrains JavaScript node_modules; there is no equivalent production install step for Cargo. I checked cdxgen's CLI docs, and --required-only is the flag that limits output to components with required scope, so dropping it globally means a Rust project with test-only/dev packages in Cargo.lock can now leak those components into the generated SBOM and notices. Please keep the omission scoped to the JavaScript/bun case, or otherwise preserve required-scope filtering for Rust scans.

Useful? React with 👍 / 👎.

Address review feedback: only omit cdxgen's --required-only for projects
that include the JavaScript ecosystem, where the flag drops shipped bun
dependencies. Non-JavaScript projects (e.g. Rust crates) keep the flag so
their behavior is unchanged.

(Verified that under --no-install-deps cdxgen assigns no scope to Rust
components, so --required-only is currently a no-op there; keeping it
scoped avoids changing the Rust invocation and is future-proof if cdxgen
starts scoping cargo dev-dependencies.)
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.

1 participant