Skip to content

SBOM ingest: preserve third-party assertions through re-export #396

Description

@bomly-guy

Context

PR #391 attempted two things at once: emitting package-origin data during export (now split out as #395) and preserving a source document's own assertions — supplier, description, external references, checksums, CPEs — when Bomly ingests an SBOM and re-exports or converts it. The ingest half was closed with #391 rather than merged: twenty review rounds kept finding real gaps in it, and the honest conclusion was that it needs a designed scope, not more patches.

Today, bomly scan --sbom --path in.cdx.json --format spdx silently drops most of what the source document asserted, because ingest is Document → sdk.Graph → Document and only coordinates, scopes, copyright, and detection licenses survive the graph hop.

What the #391 review established (design input, not optional)

The full history is on the closed PR — 100 resolved threads. The load-bearing lessons:

  1. Every restored field crosses a trust boundary. The bomly.sbom.* metadata keys ride sdk.Dependency.Metadata, which any detector or external plugin can write. Every value restored from metadata must re-clear the same publication gate it passed on ingest — the final unfixed finding was exactly this hole for external_refs.
  2. Ingested URLs are untrusted input that gets re-emitted. They need the same credential/local-path gates as detector values, but with asserted semantics: a source-declared download location keeps a benign query; a detector value does not. The gate split is documented in dev-docs/ARCHITECTURE.md on feat(sbom): emit package origin as download and repository locations #395's branch.
  3. Merge semantics must be defined per field class up front. Scalars fill gaps; sets (references, CPEs, licenses, contacts, URLs) union; contradictions (two SHA-256 values, an exact URL vs NONE) resolve by a stated precedence — detection classifies, ingest corrects, enrichment fills gaps — applied atomically (URL + comment + digests move together). Most of feat(sbom): emit package origin and preserve ingested assertions #391's late defects were one merge rule fixed in one layer and missed in its twin.
  4. Every parser of untrusted data ships with a registered fuzz target from day one (CLAUDE.md rule; the CPE validator, digest parser, and reference-URL gate each had to be retrofitted, and each fuzz target found real bugs immediately).
  5. Preservation must not corrupt Bomly's own round trip. Re-ingesting Bomly output once promoted https://rubygems.org/ to an exact download location for 130 packages. The registry-root marker comment must round-trip.

Explicitly deferred decisions

  • ssh VCS locators (git+ssh://git@host/repo): requires a carve-out in the userinfo rejection, which caused two P1 credential leaks in feat(sbom): emit package origin and preserve ingested assertions #391. Needs its own adversarial pass.
  • Metadata-only primary component: skipping it on re-ingest is documented behavior; carrying its assertions means a new document-level carrier through ToGraph/FromDepGraph.
  • Fidelity long tail: external-reference hashes, supplier contacts (privacy call), SPDX summary vs description, non-Git VCS tools, CPE 2.2 vs 2.3 grammars — all have working reference code on the closed feat(sbom): emit package origin and preserve ingested assertions #391 branch (claude/github-issue-380-ab8ba4).

Acceptance sketch

  • A supplier-rich CycloneDX document converts to SPDX and back without losing supplier, description, checksums, CPEs, or classified references.
  • A hostile document (local paths, credentials in any URL position, malformed CPEs/digests) contributes nothing to output — enforced by fuzz targets and mutation-checked tests, not just examples.
  • bomly scan --sbom --enrich keeps working (ingest must not set Dependency.Source, which feeds RegistryMatchEligible).
  • Bomly's own export → ingest → export is a fixed point for every preserved field.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions