Repository navigation
SBOM ingest: preserve third-party assertions through re-export #396
Description
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:
- Every restored field crosses a trust boundary. The
bomly.sbom.*metadata keys ridesdk.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 forexternal_refs. - 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.mdon feat(sbom): emit package origin as download and repository locations #395's branch. - 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. - 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).
- 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 --enrichkeeps working (ingest must not setDependency.Source, which feedsRegistryMatchEligible).- Bomly's own export → ingest → export is a fixed point for every preserved field.
🤖 Generated with Claude Code
Activity
docs: plan the SDK maturity program (ADR-0036 through ADR-0039) (#411)
build: require Go 1.27 (#412)
feat(sbom): export the whole scope set, and refuse a document that re…