Skip to content

feat(sbom): consistent primary component, document identity, and CRA metadata - #364

Open
bomly-guy wants to merge 1 commit into
mainfrom
claude/eloquent-bouman-e7be9a
Open

feat(sbom): consistent primary component, document identity, and CRA metadata#364
bomly-guy wants to merge 1 commit into
mainfrom
claude/eloquent-bouman-e7be9a

Conversation

@bomly-guy

Copy link
Copy Markdown
Member

Summary

Improves SBOM export quality based on findings from running sbom-tools v0.1.22 against bomly scan -o cyclonedx -o spdx output of this repo (baseline: grade F, 43.4 overall). Self-scan now scores 59.4 with zero config, with the remaining CRA errors being data we refuse to invent (see "Deliberate non-fixes").

1. Primary component identity (finding 1)

metadata.component previously fell to Roots[0] — an arbitrary manifest node (.github/workflows/auto-version.yml). When a graph has multiple roots, sbom.FromDepGraph now synthesizes a project root: named after the scanned project, application type, pkg:generic PURL, depending on every graph root. Both formats now agree on the document's subject (sbom-tools cross-format diff: 0 added / 0 removed components, was 44.7% similarity with full mismatch), and the exported graph is one connected component (islands 3 → 1, orphans 1 → 0, roots 14 → 1). Single-root graphs keep their natural root. The pseudo root is excluded from the CycloneDX component inventory and skipped on re-ingestion (round-trip verified).

2. Serial number (finding 2)

Each export generates a UUIDv4: CycloneDX serialNumber (urn:uuid:…), and the SPDX document namespace reuses the same nonce (https://bomly.dev/spdx/<uuid>), so the two exports of one scan are correlatable.

3. Tool version (finding 3)

CycloneDX metadata.tools[] now carries bomly's version; SPDX emits Creator: Tool: bomly-cli-<version> (SPDX 2.3 toolidentifier-version convention, same as syft/trivy). Note: sbom-tools' SPDX parser doesn't recognize the hyphen convention (only name vX / name (X) / name@X), so has_tool_version stays false on the SPDX side — checker quirk, not missing data.

4. Component hashes (finding 4)

  • Detection-time dependency digests (npm/pnpm/yarn/bun SRI integrity, nuget, cocoapods) are now projected into SBOM components — previously only registry (enrichment) digests were. Values are normalized to lowercase hex (npm SRI is base64) so they're schema-valid.
  • The gomod detector now parses go.sum and attaches h1: tree hashes as sha256 (hex), matching cyclonedx-gomod's convention. Self-scan: 89% of components carry hashes (was 0%).
  • Deliberate non-fixes: per-component supplier and description are not cheaply fillable without enrichment data sources that provide them; we don't fabricate them to satisfy profile checkers. Licenses without --enrich remain empty as expected.

5. Dependency graph verification (finding 5)

The CDX dependencies section was already complete: all 343 components have an entry; "188/344 with edges" is simply the non-leaf count. The 3 reported cycles are real Go module-graph cycles (go.opentelemetry.io/otel*, grpc/envoyproxy/cncf-xds, cloud.google.com/go/auth*) — module-level cycles are legitimate in Go, not export artifacts. The islands/orphan were the disconnected manifest roots, fixed by the synthesized project root. The residual cross-format semantic-similarity gap (~44%) is an sbom-tools comparator artifact: it derives CDX edge scope from component scope and has no SPDX equivalent (verified: stripping scope from our CDX yields 99.97% similarity; scoped SPDX relationship types don't map either). We keep scope data rather than degrade the export.

6. CRA-readiness metadata (finding 6)

New optional sbom: config section (config file + env vars, no new CLI flags):

sbom:
  manufacturer: "Example Org"                                   # CRA Art. 13(15)
  security_contact: "security@example.com"                      # CRA Art. 13(6)
  vulnerability_disclosure_url: "https://example.com/security"  # CRA Art. 13(7)
  support_end: "2030-12-31"                                     # CRA Art. 13(8)

CycloneDX: metadata.manufacturer, security-contact/advisories external refs on the primary component, bomly:support_end_date property. SPDX: Organization creator, primary-package supplier, creation-info comment. All four verified recognized by sbom-tools' CRA profile. support_end is validated as an ISO date.

Validation

  • make test, make lint, make generate all clean.
  • Both outputs validate with 0 errors against the official CycloneDX 1.7 and SPDX 2.3 JSON schemas.
  • NTIA minimum elements: COMPLIANT (0 errors) for both formats, before and after.
  • Re-ingestion round-trip (--sbom scans of our own exports) produces identical dependency counts, with the pseudo root correctly excluded.

Notes for reviewers

  • No smoke goldens contain SBOM documents and detection-time digests don't appear in scan JSON, so no golden regeneration is expected; if nightly smoke disagrees, dispatch Update Smoke Goldens.
  • The SBOM interoperability assurance workflow (linux-only validators) should be dispatched on this branch or after merge for the external-validator pass.
  • Feature-checklist deviation: the CRA knobs are config/env only (per the "optional config" framing) — no CLI flags, hence no MCP/flag/completion wiring. MCP doesn't export SBOM documents, so nothing to mirror there.

🤖 Generated with Claude Code

…metadata

Improve SBOM export quality based on third-party validator findings
(sbom-tools v0.1.22 reported grade F / 43.4 overall on our own scan):

- Primary component: synthesize a project root (application type,
  pkg:generic PURL) when the graph has multiple roots, instead of letting
  CycloneDX metadata.component fall to an arbitrary manifest node
  (previously a .github workflow file). The root depends on every graph
  root, so the exported dependency graph is one connected component, and
  both formats now name the same scanned project (cross-format component
  identity: 0 added / 0 removed in sbom-tools diff). Single-root graphs
  keep their natural root. Re-ingestion skips the pseudo root.
- Document identity: generate a UUIDv4 per export; CycloneDX gets a
  urn:uuid serialNumber and the SPDX namespace reuses the same nonce.
- Tool version: emit bomly's version in CycloneDX metadata.tools and as
  the SPDX "bomly-cli-<version>" tool creator.
- Component hashes: project detection-time dependency digests (npm SRI
  integrity, nuget, cocoapods) into SBOM components, normalized to
  lowercase hex; parse go.sum h1 tree hashes in the gomod detector and
  expose them as sha256 (cyclonedx-gomod convention). 89% of components
  in a self-scan now carry hashes (was 0%).
- CRA readiness: optional sbom config section (manufacturer,
  security_contact, vulnerability_disclosure_url, support_end) emitted as
  CycloneDX metadata.manufacturer, security-contact/advisories external
  references, and a support-end property; SPDX gets an Organization
  creator, root package supplier, and creation-info comment.

Self-scan score moves 43.4 (F) to 59.4 with no config and to fully
recognized manufacturer/contact/support fields with the sbom section set.
NTIA minimum elements remain compliant for both formats; both outputs
validate clean against the official CycloneDX 1.7 and SPDX 2.3 schemas.
The 3 cycles the validator reported are real Go module-graph cycles
(otel, grpc/xds, cloud auth), not export artifacts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@bomly-guy, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 685d9992-bd04-4fa5-b87e-f80af6d22b07

📥 Commits

Reviewing files that changed from the base of the PR and between 55b347a and 21d200d.

⛔ Files ignored due to path filters (1)
  • docs/CONFIG_REFERENCE.md is excluded by !docs/CONFIG_REFERENCE.md
📒 Files selected for processing (15)
  • dev-docs/ARCHITECTURE.md
  • docs/SBOM.md
  • internal/cli/scan_cmd.go
  • internal/config/config.go
  • internal/config/load.go
  • internal/config/validate_test.go
  • internal/detectors/gomod/detector.go
  • internal/detectors/gomod/detector_test.go
  • internal/detectors/gomod/parser_fuzz_test.go
  • internal/sbom/cyclonedx.go
  • internal/sbom/export_quality_test.go
  • internal/sbom/graph.go
  • internal/sbom/model.go
  • internal/sbom/spdx23.go
  • internal/sbom/transform.go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Bomly Diff Summary

Compared 55b347a31046ebb7ead483f6180a505fc46bb13d to 21d200d8281cdcd8e9a752c721b3696fb41686d6.

Overview

Status Manifests Dependencies Findings Duration
✅ Pass +0 / ~0 / -0 0 added / 0 version changed / 0 detail changes / 0 removed 0 introduced / 0 persisted / 0 resolved 1m 18s

Dependency Changes

✅ No dependency changes.

Vulnerabilities

✅ No vulnerability changes.

License Changes

✅ No license changes.

Project Posture

✅ No project posture changes (--matchers +scorecard was not selected).

Policy Findings

✅ No policy differences were identified.

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