Skip to content

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

Open
bomly-guy wants to merge 3 commits into
mainfrom
claude/eloquent-bouman-e7be9a
Open

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

Conversation

@bomly-guy

@bomly-guy bomly-guy commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

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


Follow-up commit: lifecycle, completeness, versions, remediation, richer digests

Per review direction, the roadmap quick wins are folded into this PR — implemented as general exporter quality for both formats, never score-chasing:

  • Project version: primary component and first-party modules get --ref or git describe output; omitted when Git has nothing (no invented versions). Clears the last two CRA component-version errors.
  • CycloneDX lifecycles + compositions: pre-build/post-build phase by target type; completeness declared complete only for unfiltered, warning-free scans — incomplete under --scope, unknown on degraded resolution.
  • CycloneDX metadata.authors with the configured contact email.
  • Vulnerability recommendation rendered from enrichment-known fixed versions only.
  • License normalization (both formats): deprecated SPDX ids rewritten token-wise in expressions (GPL-2.0GPL-2.0-only); free text untouched.
  • SPDX primaryPackagePurpose on every package; decode still prefers the bomly:type comment so round-trips keep domain types SPDX can't name.
  • GitHub Actions integrity: SHA-256 of workflow/action manifest files; pinned commit ID (sha1) for SHA-pinned actions.

Self-scan with --enrich + sbom: config: CycloneDX 80.9 (B), SPDX 78.2 (baseline was 43.4/44 F; sbom-tools' own released SBOM scores 68.4). Vulnerability metrics 100, integrity 95.9, zero deprecated license ids, both formats schema-clean against official CycloneDX 1.7/SPDX 2.3 schemas, NTIA-compliant, round-trip re-ingestion verified. Remaining known gaps are deliberate or roadmap: per-component supplier/description need a real data source (deps.dev enrichment), and BOM signing is a separate feature.

Summary by CodeRabbit

  • New Features
    • Enriched SBOM exports with project metadata, lifecycle and completeness details, provenance, package purposes, digests, and vulnerability recommendations.
    • Added support for manufacturer, security contact, disclosure URL, and support-end date configuration.
    • Improved multi-root projects with synthesized primary components and consistent document identities across CycloneDX and SPDX.
    • Added automatic version detection from Git when no version is specified.
  • Bug Fixes
    • Improved handling of incomplete dependency metadata and legacy SBOM inputs without export failures.
  • Documentation
    • Expanded SBOM and architecture documentation.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 53 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: 409b296e-af3d-4012-8049-8e1f1f87c62e

📥 Commits

Reviewing files that changed from the base of the PR and between fdd45a2 and b1b5656.

📒 Files selected for processing (2)
  • internal/sbom/export_quality_test.go
  • internal/sbom/transform.go
📝 Walkthrough

Walkthrough

The PR enriches SBOM generation with provenance, lifecycle, completeness, version, digest, license, vulnerability, and package-purpose metadata. It adds synthesized roots for multi-root graphs and updates CycloneDX/SPDX conversion, validation, round-trip handling, and documentation.

Changes

SBOM enrichment

Layer / File(s) Summary
Scan metadata and provenance wiring
internal/cli/scan_cmd.go, internal/cli/scan_cmd_test.go, internal/config/*
Scan options now include lifecycle, completeness, version, project, and configured provenance metadata. Support dates use ISO validation.
Dependency digest extraction
internal/detectors/githubactions/*, internal/detectors/gomod/*
GitHub Actions manifests and Go modules now provide validated SHA-1 or SHA-256 digests when metadata is available.
Project-root graph transformation
internal/sbom/model.go, internal/sbom/transform.go, internal/sbom/graph.go, internal/sbom/export_quality_test.go
Multi-root graphs receive synthesized project roots. Documents use UUID-based identity. Digests, versions, vulnerability recommendations, and SPDX licenses are normalized.
CycloneDX and SPDX export contracts
internal/sbom/cyclonedx.go, internal/sbom/spdx23.go, internal/sbom/export_quality_test.go, docs/SBOM.md, dev-docs/ARCHITECTURE.md
Exports now preserve project metadata, provenance, lifecycle, composition, package purposes, tool versions, remediation recommendations, and synthetic-root relationships. Documentation describes the new behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant ScanCommand
  participant FromDepGraph
  participant SBOMEncoder
  participant ToGraph
  ScanCommand->>FromDepGraph: pass scan metadata and dependency graph
  FromDepGraph->>SBOMEncoder: create enriched CycloneDX or SPDX document
  SBOMEncoder->>ToGraph: decode components and dependency relationships
  ToGraph-->>ScanCommand: reconstruct graph without synthetic root edges
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the PR's main SBOM changes, including primary components, document identity, and CRA metadata.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/eloquent-bouman-e7be9a

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 190f28ee7185a3cbb877aa5e7e7fb415bb7ef669 to b1b5656172ac54481ebfcae66123a37b3bee691a.

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 15s

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.

@bomly-guy

Copy link
Copy Markdown
Collaborator Author

Measured this branch's SBOM output against the just-released sbom-tools v0.2.0 (their "correctness release"), same protocol as the issue-#361 baseline, same scan target:

Metric (CycloneDX, no --enrich) main baseline this PR
Overall quality (standard profile) 42.8/100 (F) 57.1/100
Completeness 41.7 58.0
Identifiers 99.7 100
Components with hashes 0/344 306/343
sbomqs-model NTIA score 7.1/10 8.6/10
CISA-2026 profile errors 1,035 729

Primary component, serialNumber, and tool version all verified fixed. Two findings for follow-up (here or #361):

  1. The synthesized primary component (bomly-cli) has no version field — flagged as an ERROR by both the NTIA and CRA profiles ("Component version | bomly-cli"). Easy add since the CLI knows its scan-target context.
  2. Heads-up: under validator v0.2.0 the old "NTIA compliant" observation from SBOM export: fix CycloneDX metadata.component naming and close completeness gaps #361 no longer holds for anyone's output shaped like ours — per-component supplier gaps are now errors (344 of them), not warnings. That's the dominant remaining metric gap and is genuinely hard for Go modules without enrichment-derived supplier data; fine to treat as out of scope for this PR.

🤖 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>
@bomly-guy
bomly-guy force-pushed the claude/eloquent-bouman-e7be9a branch from 21d200d to 1384a9f Compare August 11, 2026 23:54
…r digests

Second round of export-quality improvements, applied to both formats where
the format can express them:

- Project version: stamp the primary component and first-party (main
  module) components from --ref or `git describe --tags --always --dirty`;
  omit rather than invent when Git has nothing to say. Third-party
  versions are never touched.
- CycloneDX lifecycles (`pre-build` for source scans, `post-build` for
  container images) and a composition completeness declaration that only
  claims `complete` for unfiltered, warning-free scans (`incomplete`
  under --scope, `unknown` on degraded resolution).
- CycloneDX metadata.authors (manufacturer + contact email) alongside the
  existing manufacturer entity.
- Vulnerability `recommendation` derived from enrichment-known fixed
  versions ("Upgrade <pkg> to <version>"); nothing is emitted when no fix
  is known.
- SPDX license normalization shared by both formats: deprecated SPDX ids
  are rewritten token-wise inside expressions (GPL-2.0 -> GPL-2.0-only);
  free-text values pass through.
- SPDX PrimaryPackagePurpose on every package (LIBRARY default,
  APPLICATION for the primary, OTHER for domain types SPDX cannot name);
  decode still prefers the bomly:type comment so round-trips keep richer
  types.
- GitHub Actions integrity: SHA-256 digests of workflow/action manifest
  files and the pinned commit ID (sha1) of SHA-pinned actions.

Self-scan with --enrich and the sbom config section: CycloneDX 80.9 (B),
SPDX 78.2 — from 43.4/44 (F) at baseline. Vulnerability metrics 100,
integrity 95.9, zero deprecated license ids, both formats schema-clean
and NTIA-compliant. The one remaining CRA error (per-component supplier)
is deliberate: supplier data is not fabricated without a real source.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
internal/sbom/transform.go (1)

428-456: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add native fuzz coverage for normalizeSPDXLicenseExpression.

The existing SBOM fuzz target does not reach componentLicenses or this helper. Add bounded valid, malformed, and truncated seeds, then assert panic safety and deterministic output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/sbom/transform.go` around lines 428 - 456, Add native fuzz coverage
targeting normalizeSPDXLicenseExpression, including bounded seeds for valid,
malformed, and truncated SPDX expressions. The fuzz test must assert that the
helper never panics and returns deterministic output for identical input, while
avoiding unbounded input growth.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/SBOM.md`:
- Around line 151-154: Update the supplier fallback statement in the SBOM
documentation to accurately state that SPDX PackageSupplier remains unset unless
manufacturer metadata is configured or another data source supplies it; do not
claim it defaults to the producing tool unless that fallback is implemented.

In `@internal/cli/scan_cmd.go`:
- Around line 274-290: Add a DEBUG log in gitDescribeVersion immediately before
cmd.Output(), including the git executable path, arguments, and working
directory path so the subprocess can be reproduced. Keep the existing command
execution and error handling unchanged.

In `@internal/config/config.go`:
- Around line 85-88: Add the required configuration metadata for all new SBOM
fields in Resolved and the nested SBOMFile leaf fields: retain doc and env tags,
add appropriate default tags to Resolved, and add yaml, resolved, legacy, and
pointer-backed field definitions to SBOMFile. Ensure each field follows the
existing configuration tag conventions.

In `@internal/config/load.go`:
- Around line 369-370: Update the validation error returned by the time.Parse
call in the SBOM support-end handling to wrap the original parsing error with %w
while retaining the existing contextual message and configured date value.

In `@internal/detectors/gomod/detector.go`:
- Around line 402-434: Add a native Go fuzz test named FuzzParseGoSumDigests for
parseGoSumDigests, seeding valid, malformed, and truncated go.sum inputs. Bound
fuzz data using testutil.MaxFuzzInputSize, invoke the parser through the
repository-file test setup, and assert repeated parsing produces deterministic
results without panics.

In `@internal/sbom/spdx23.go`:
- Around line 48-52: Update the SPDX package conversion around
IsProjectRootComponent to also recognize document roots from doc.Roots, using a
root-component set so natural primary packages receive PackageSupplier when
doc.Provenance.Manufacturer is set. Preserve existing synthetic-root handling,
and add coverage for a single-root document with a manufacturer.

---

Nitpick comments:
In `@internal/sbom/transform.go`:
- Around line 428-456: Add native fuzz coverage targeting
normalizeSPDXLicenseExpression, including bounded seeds for valid, malformed,
and truncated SPDX expressions. The fuzz test must assert that the helper never
panics and returns deterministic output for identical input, while avoiding
unbounded input growth.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 954e1075-4ea4-4485-a6b9-0827ea700d53

📥 Commits

Reviewing files that changed from the base of the PR and between 190f28e and fdd45a2.

⛔ Files ignored due to path filters (2)
  • docs/CONFIG_REFERENCE.md is excluded by !docs/CONFIG_REFERENCE.md
  • internal/detectors/gomod/testdata/demo/go.sum is excluded by !**/*.sum, !**/testdata/**
📒 Files selected for processing (18)
  • dev-docs/ARCHITECTURE.md
  • docs/SBOM.md
  • internal/cli/scan_cmd.go
  • internal/cli/scan_cmd_test.go
  • internal/config/config.go
  • internal/config/load.go
  • internal/config/validate_test.go
  • internal/detectors/githubactions/detector.go
  • internal/detectors/githubactions/detector_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

Comment thread docs/SBOM.md
Comment on lines +151 to +154
Without these fields Bomly's exports satisfy the NTIA minimum elements
(supplier defaults to the producing tool); third-party CRA profile checks
will flag the missing manufacturer/contact metadata until the `sbom` section
is configured. Per-component supplier and description data is not invented:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the supplier fallback statement.

The exporter does not default a package supplier to the producing tool. SPDX sets PackageSupplier only when manufacturer is configured. State that supplier metadata remains absent until a data source provides it, or implement the documented fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/SBOM.md` around lines 151 - 154, Update the supplier fallback statement
in the SBOM documentation to accurately state that SPDX PackageSupplier remains
unset unless manufacturer metadata is configured or another data source supplies
it; do not claim it defaults to the producing tool unless that fallback is
implemented.

Comment thread internal/cli/scan_cmd.go
Comment on lines +274 to +290
// gitDescribeVersion derives a project version from Git history when the scan
// target is a checkout with no explicit ref (local path scans). Returns ""
// when Git or history is unavailable — the version is then simply omitted.
func gitDescribeVersion(path string) string {
if strings.TrimSpace(path) == "" {
return ""
}
gitPath, err := system.LookPath("git")
if err != nil {
return ""
}
cmd := system.Command(gitPath, "-C", path, "describe", "--tags", "--always", "--dirty")
out, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Log the Git command before execution.

Line 285 starts a subprocess. It does not log the executable path, arguments, and working directory at DEBUG level. Emit the required DEBUG log before cmd.Output().

As per coding guidelines, “When invoking subprocesses, DEBUG logs must include the binary path, arguments, and working directory so the command can be reproduced.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/scan_cmd.go` around lines 274 - 290, Add a DEBUG log in
gitDescribeVersion immediately before cmd.Output(), including the git executable
path, arguments, and working directory path so the subprocess can be reproduced.
Keep the existing command execution and error handling unchanged.

Source: Coding guidelines

Comment thread internal/config/config.go
Comment on lines +85 to +88
SBOMManufacturer string `doc:"Organization name emitted as the SBOM manufacturer/supplier (EU CRA Art. 13(15))" env:"BOMLY_SBOM_MANUFACTURER"`
SBOMSecurityContact string `doc:"Security contact URL or email emitted in exported SBOMs (EU CRA Art. 13(6))" env:"BOMLY_SBOM_SECURITY_CONTACT"`
SBOMVulnerabilityDisclosureURL string `doc:"Coordinated vulnerability disclosure policy URL emitted in exported SBOMs (EU CRA Art. 13(7))" env:"BOMLY_SBOM_VULNERABILITY_DISCLOSURE_URL"`
SBOMSupportEnd string `doc:"Support end date (YYYY-MM-DD) for security updates emitted in exported SBOMs (EU CRA Art. 13(8))" env:"BOMLY_SBOM_SUPPORT_END"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required configuration tags.

The new Resolved fields omit default: tags. The new SBOMFile leaf fields omit legacy: tags. Add the required tags for each field.

As per coding guidelines, “Add new configuration fields to Resolved with doc:, env:, and default: tags and to the nested File leaf with yaml:, resolved:, legacy legacy: tags, and pointer-backed shape.”

Also applies to: 109-114

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/config.go` around lines 85 - 88, Add the required
configuration metadata for all new SBOM fields in Resolved and the nested
SBOMFile leaf fields: retain doc and env tags, add appropriate default tags to
Resolved, and add yaml, resolved, legacy, and pointer-backed field definitions
to SBOMFile. Ensure each field follows the existing configuration tag
conventions.

Source: Coding guidelines

Comment thread internal/config/load.go
Comment on lines +369 to +370
if _, err := time.Parse("2006-01-02", supportEnd); err != nil {
return fmt.Errorf("invalid sbom support_end %q: expected an ISO date such as 2030-12-31", cfg.SBOMSupportEnd)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Wrap the date parsing error.

Preserve the time.Parse error with %w. This retains the operation context and the parse failure details.

As per coding guidelines: “Always wrap errors with contextual information using %w.”

Proposed fix
- return fmt.Errorf("invalid sbom support_end %q: expected an ISO date such as 2030-12-31", cfg.SBOMSupportEnd)
+ return fmt.Errorf("validate sbom support_end %q: expected an ISO date such as 2030-12-31: %w", supportEnd, err)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if _, err := time.Parse("2006-01-02", supportEnd); err != nil {
return fmt.Errorf("invalid sbom support_end %q: expected an ISO date such as 2030-12-31", cfg.SBOMSupportEnd)
if _, err := time.Parse("2006-01-02", supportEnd); err != nil {
return fmt.Errorf("invalid sbom support_end %q: expected an ISO date such as 2030-12-31: %w", supportEnd, err)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/load.go` around lines 369 - 370, Update the validation error
returned by the time.Parse call in the SBOM support-end handling to wrap the
original parsing error with %w while retaining the existing contextual message
and configured date value.

Source: Coding guidelines

Comment on lines +402 to +434
// parseGoSumDigests reads go.sum and returns a "path@version" → digest map for
// module tree hashes ("h1:" lines, excluding the "/go.mod" entries). The h1
// hash is SHA-256 over the Go module dirhash manifest; it is exposed as a
// sha256 digest in hex, matching the convention CycloneDX's cyclonedx-gomod
// uses for Go module component hashes.
func parseGoSumDigests(path string) (map[string]sdk.Digest, error) {
data, err := system.ReadRepositoryFile(path)
if err != nil {
return nil, fmt.Errorf("read %q: %w", path, err)
}

digests := make(map[string]sdk.Digest)
scanner := bufio.NewScanner(strings.NewReader(strings.ReplaceAll(string(data), "\r\n", "\n")))
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) != 3 {
continue
}
modulePath, version, hash := fields[0], fields[1], fields[2]
if strings.HasSuffix(version, "/go.mod") || !strings.HasPrefix(hash, "h1:") {
continue
}
raw, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(hash, "h1:"))
if err != nil || len(raw) != sha256.Size {
continue
}
digests[modulePath+"@"+version] = sdk.Digest{Algorithm: sdk.DigestAlgorithmSHA256, Value: hex.EncodeToString(raw)}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("scan %q: %w", path, err)
}
return digests, nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a fuzz target for parseGoSumDigests.

parseGoSumDigests parses untrusted repository data. The existing fuzz target only calls depGraphFromGoListWithScope. Add FuzzParseGoSumDigests with valid, malformed, and truncated seeds. Bound input with testutil.MaxFuzzInputSize and assert deterministic results.

As per coding guidelines, “New or materially changed pure in-process parsers for untrusted repository ... data must have native Go fuzz targets.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/detectors/gomod/detector.go` around lines 402 - 434, Add a native Go
fuzz test named FuzzParseGoSumDigests for parseGoSumDigests, seeding valid,
malformed, and truncated go.sum inputs. Bound fuzz data using
testutil.MaxFuzzInputSize, invoke the parser through the repository-file test
setup, and assert repeated parsing produces deterministic results without
panics.

Source: Coding guidelines

Comment thread internal/sbom/spdx23.go
Comment on lines +48 to +52
if IsProjectRootComponent(c) {
if doc.Provenance.Manufacturer != "" {
pkg.PackageSupplier = &common.Supplier{SupplierType: "Organization", Supplier: doc.Provenance.Manufacturer}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Assign the supplier to natural primary packages too.

A single-root graph does not create a synthetic component. Its DESCRIBES package therefore does not receive PackageSupplier, even when doc.Provenance.Manufacturer is set. CycloneDX attaches provenance to either primary-component form, so the SPDX export now diverges.

Build a set from doc.Roots and set the supplier when the current component is a document root. Add coverage for a single-root document with a manufacturer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/sbom/spdx23.go` around lines 48 - 52, Update the SPDX package
conversion around IsProjectRootComponent to also recognize document roots from
doc.Roots, using a root-component set so natural primary packages receive
PackageSupplier when doc.Provenance.Manufacturer is set. Preserve existing
synthetic-root handling, and add coverage for a single-root document with a
manufacturer.

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

Copy link
Copy Markdown
Collaborator Author

Follow-up enhancements for the deliberately-out-of-scope items are now tracked: #380 (per-component supplier/description from real enrichment sources — also the last remaining CRA profile error) and #381 (signing generated SBOMs).

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