From 7d4971e490aabccd26abeb21b7a3ea2149353f29 Mon Sep 17 00:00:00 2001 From: Ahmed ElMallah Date: Thu, 30 Jul 2026 04:18:59 -0700 Subject: [PATCH] feat(sbom): write CycloneDX 1.7 by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The codec and cyclonedx-go v0.11.0 already supported 1.7 end to end (targets, encoder cases, ingest auto-detection for 1.4-1.7); only the write default still selected 1.6. Flip the two selection points (render.SBOMTarget, benchmark bomlySBOMTarget), point the interop assurance at v1_7 (the pinned cyclonedx-cli v0.32.0 validator already accepts it — verified by running it against a generated 1.7 document), and update the recorded workflow checksum in the evidence catalog. Ingest is unchanged: CycloneDX 1.4-1.7 and SPDX 2.3 remain accepted, and the existing 1.6 smoke fixture keeps exercising 1.6 ingest. Docs sweep updates every current-behavior mention of 1.6 (SBOM, output formats, getting started, glossary, scan targets, README, faq.json, manifest, evidence pages); the dated July 24 workflow-run statement in EVIDENCE.md intentionally keeps saying 1.6. Also fixes stale detector prose that claimed only 1.6 ingest was supported and 1.5 was future work — 1.4-1.7 have all been accepted by the codec. Verified: emitted document reports specVersion 1.7 and passes cyclonedx-cli v0.32.0 validation with --input-version v1_7; ingest round-trips the generated 1.7 file; make evidence, make fuzz, make generate, and the full test suite pass. Co-Authored-By: Claude Fable 5 --- .github/workflows/sbom-interoperability.yml | 2 +- README.md | 4 ++-- docs/GETTING_STARTED.md | 2 +- docs/GLOSSARY.md | 2 +- docs/OUTPUT_FORMATS.md | 4 ++-- docs/SBOM.md | 4 ++-- docs/SCAN_TARGETS.md | 2 +- docs/detectors/ecosystems/sbom/sbom.md | 4 ++-- docs/evidence/TARGETS_AND_OPERATIONS.md | 2 +- docs/faq.json | 4 ++-- docs/manifest.json | 2 +- internal/benchmark/run.go | 2 +- internal/cli/render/sbom.go | 2 +- internal/cli/render/sbom_test.go | 6 +++--- internal/output/output.go | 2 +- internal/support/prose/detectors/sbom.md | 4 ++-- internal/tools/sbomassurance/main.go | 4 ++-- test/evidence/cases.json | 4 ++-- 18 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/sbom-interoperability.yml b/.github/workflows/sbom-interoperability.yml index 75c84afa..d378820d 100644 --- a/.github/workflows/sbom-interoperability.yml +++ b/.github/workflows/sbom-interoperability.yml @@ -65,7 +65,7 @@ jobs: fi { printf '# SBOM compatibility check: %s %s\n\n' "${icon}" "${result}" - printf 'Bomly generated SPDX 2.3 and CycloneDX 1.6 files, then asked the official validators to check them.\n\n' + printf 'Bomly generated SPDX 2.3 and CycloneDX 1.7 files, then asked the official validators to check them.\n\n' printf -- '- **Revision:** `%s`\n' "${GITHUB_SHA}" printf -- '- **Validation:** `%s`\n' "${VALIDATION_RESULT}" printf -- '- **Evidence:** %s\n\n' "${evidence}" diff --git a/README.md b/README.md index d14dd86e..206910c2 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ See [Getting Started](docs/GETTING_STARTED.md) for the first-run walkthrough and ## Generate Output for CI -Bomly can write human-readable text, JSON, SARIF, SPDX 2.3, and CycloneDX 1.6: +Bomly can write human-readable text, JSON, SARIF, SPDX 2.3, and CycloneDX 1.7: ```bash # Structured JSON for automation @@ -220,7 +220,7 @@ See [Plugins](docs/PLUGINS.md) for install, trust, and authoring guidance. - [Use Cases](docs/USE_CASES.md) - practical recipes for PR gates, SBOMs, triage, and offline scans - [Scan Targets](docs/SCAN_TARGETS.md) - directories, Git repos, containers, and SBOMs - [Output Formats](docs/OUTPUT_FORMATS.md) - text, JSON, SARIF, SPDX, CycloneDX -- [SBOM Formats](docs/SBOM.md) - SPDX 2.3 and CycloneDX 1.6, ingest, and conversion recipes +- [SBOM Formats](docs/SBOM.md) - SPDX 2.3 and CycloneDX 1.7, ingest, and conversion recipes - [CI Integration](docs/CI_INTEGRATION.md) - GitHub Actions, GitLab, Jenkins, Azure, CircleCI - [Bomly Guard](docs/BOMLY_GUARD.md) - turnkey GitHub Action for PR dependency review - [MCP Server](docs/MCP.md) - connect Bomly to Claude Code, Cursor, VS Code, or another MCP client diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 10d0463c..e56ff21d 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -99,7 +99,7 @@ This calls the enabled built-in matchers, including OSV, KEV, deps.dev, and Open ## Generate an SBOM -Use `-o` to write SPDX 2.3 or CycloneDX 1.6: +Use `-o` to write SPDX 2.3 or CycloneDX 1.7: ```bash bomly scan \ diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md index 6c907465..91f2695c 100644 --- a/docs/GLOSSARY.md +++ b/docs/GLOSSARY.md @@ -56,7 +56,7 @@ separate network behavior. ## SBOM -**SBOM** — Software Bill of Materials. Bomly writes SPDX 2.3 and CycloneDX 1.6 JSON via `-o`, and ingests both via `--sbom`. See [SBOM formats](SBOM.md). +**SBOM** — Software Bill of Materials. Bomly writes SPDX 2.3 and CycloneDX 1.7 JSON via `-o`, and ingests SPDX 2.3 and CycloneDX 1.4–1.7 via `--sbom`. See [SBOM formats](SBOM.md). **PURL** — Package URL identifier (`pkg:type/namespace/name@version`). Bomly emits PURLs on every package. diff --git a/docs/OUTPUT_FORMATS.md b/docs/OUTPUT_FORMATS.md index 65444148..51a27401 100644 --- a/docs/OUTPUT_FORMATS.md +++ b/docs/OUTPUT_FORMATS.md @@ -13,7 +13,7 @@ Use `--json` as a shortcut for `--format json` when you want structured output q | `markdown` | Reviews | Job summaries, PR comments, and other Markdown surfaces | | `sarif` | Audit-only | CI security panes, GitHub Security tab, IDE problem markers | | `spdx` | Scan only | SPDX 2.3 JSON SBOMs | -| `cyclonedx` | Scan only | CycloneDX 1.6 JSON SBOMs | +| `cyclonedx` | Scan only | CycloneDX 1.7 JSON SBOMs | Flag: @@ -216,7 +216,7 @@ Supported targets: | `markdown` | GitHub-flavored Markdown report | | `sarif` | SARIF 2.1.0 report; requires `--audit` | | `spdx` | SPDX 2.3 JSON | -| `cyclonedx` | CycloneDX 1.6 JSON | +| `cyclonedx` | CycloneDX 1.7 JSON | `spdx` and `cyclonedx` are supported by `scan`. Report formats (`text`, `json`, `markdown`, `sarif`) are supported by report-producing commands. See [SBOM formats](SBOM.md) for the SBOM comparison and writing rules. diff --git a/docs/SBOM.md b/docs/SBOM.md index 2829242b..5c46fef3 100644 --- a/docs/SBOM.md +++ b/docs/SBOM.md @@ -1,6 +1,6 @@ # SBOM Formats -Bomly reads and writes the two open SBOM standards used in production today: SPDX 2.3 and CycloneDX 1.6. +Bomly reads and writes the two open SBOM standards used in production today. It writes SPDX 2.3 and CycloneDX 1.7, and ingests SPDX 2.3 plus CycloneDX 1.4 through 1.7. ## What's an SBOM? @@ -10,7 +10,7 @@ You produce an SBOM once and consume it many times: in PR checks, in release art ## Format comparison -| | SPDX 2.3 | CycloneDX 1.6 | +| | SPDX 2.3 | CycloneDX 1.7 | | --- | --- | --- | | Steward | Linux Foundation | OWASP | | Primary use case | Software supply chain and license compliance | Component analysis and vulnerability management | diff --git a/docs/SCAN_TARGETS.md b/docs/SCAN_TARGETS.md index f35b342d..8d2b0028 100644 --- a/docs/SCAN_TARGETS.md +++ b/docs/SCAN_TARGETS.md @@ -186,7 +186,7 @@ bomly scan --sbom --path ./vendor.spdx.json bomly scan --sbom --path ./build/sbom.cdx.json ``` -SPDX 2.3 JSON and CycloneDX 1.6 JSON are auto-detected. Useful when: +SPDX 2.3 JSON and CycloneDX 1.4–1.7 JSON are auto-detected. Useful when: - You produced an SBOM in a previous CI step and want to audit it. - A vendor sent you an SBOM and you want to evaluate it against your policy. diff --git a/docs/detectors/ecosystems/sbom/sbom.md b/docs/detectors/ecosystems/sbom/sbom.md index 4339b009..8e14365c 100644 --- a/docs/detectors/ecosystems/sbom/sbom.md +++ b/docs/detectors/ecosystems/sbom/sbom.md @@ -19,7 +19,7 @@ Bomly uses this chain when it finds `sbom` evidence. ## How `sbom` resolves -`sbom-detector` is a **direct ingest** — it accepts SPDX 2.3 JSON or CycloneDX 1.6 JSON as input and treats the SBOM as the source of truth for the graph. No subprocess runs, no other detector chains fire. +`sbom-detector` is a **direct ingest** — it accepts SPDX 2.3 JSON or CycloneDX 1.4–1.7 JSON as input and treats the SBOM as the source of truth for the graph. No subprocess runs, no other detector chains fire. | Step | Strategy | Command | | --- | --- | --- | @@ -78,5 +78,5 @@ bomly diff --sbom --base ./v1.0.cdx.json --head ./v1.1.cdx.json - **Relationship fidelity depends on the source SBOM.** If the SBOM was produced by a tool that emits a flat package list (no `DEPENDS_ON` / `dependencies` edges), Bomly's graph is also flat. `bomly explain` cannot show paths that aren't recorded. - **Vendor-specific extensions** (custom properties, non-standard package types) are passed through to the JSON output but are not used for policy decisions. - **SBOM ingest is exclusive** — combining `--sbom` with `--image` or `--url` is rejected with exit 4. -- **Format versions other than SPDX 2.3 JSON and CycloneDX 1.6 JSON** are rejected. SPDX 3.0 and CycloneDX 1.5 ingest are tracked for follow-up. +- **Format versions other than SPDX 2.3 JSON and CycloneDX 1.4–1.7 JSON** are rejected. SPDX 3.0 ingest is tracked for follow-up. - **Tag-Value SPDX** and **XML CycloneDX** are not currently ingested. diff --git a/docs/evidence/TARGETS_AND_OPERATIONS.md b/docs/evidence/TARGETS_AND_OPERATIONS.md index 20043dc8..1f9c1416 100644 --- a/docs/evidence/TARGETS_AND_OPERATIONS.md +++ b/docs/evidence/TARGETS_AND_OPERATIONS.md @@ -47,7 +47,7 @@ started interoperability workflow adds an external check: gh workflow run sbom-interoperability.yml --ref v0.20.0 ``` -It generates canonical SPDX 2.3 and CycloneDX 1.6 files, verifies the +It generates canonical SPDX 2.3 and CycloneDX 1.7 files, verifies the downloaded validator checksums, runs the named official validators, and saves the generated files plus a `bomly.sbom-assurance-run/v1` report. See [`test/assurance/SBOM_INTEROPERABILITY.md`](../../test/assurance/SBOM_INTEROPERABILITY.md) diff --git a/docs/faq.json b/docs/faq.json index 3121d6a0..acb75e14 100644 --- a/docs/faq.json +++ b/docs/faq.json @@ -165,7 +165,7 @@ "id": "can-bomly-read-or-generate-sboms", "question": "Can Bomly read or generate SBOMs?", "toc": "Reading & generating SBOMs", - "answer": "Both. `bomly scan --sbom --path ./sbom.cdx.json` reads an existing SPDX or CycloneDX SBOM and turns it into a dependency graph you can diff, explain, and audit. Bomly also writes SBOMs in SPDX 2.3 and CycloneDX 1.6, either to stdout or to files while still printing the normal report.", + "answer": "Both. `bomly scan --sbom --path ./sbom.cdx.json` reads an existing SPDX or CycloneDX SBOM and turns it into a dependency graph you can diff, explain, and audit. Bomly also writes SBOMs in SPDX 2.3 and CycloneDX 1.7, either to stdout or to files, and ingests CycloneDX 1.4 through 1.7.", "links": [ { "label": "SBOM guide", @@ -294,7 +294,7 @@ "id": "output-formats", "question": "What output formats does Bomly produce?", "toc": "Output formats", - "answer": "The default output is a human-readable text report. With `--format` you can switch to `json`, `markdown`, SARIF 2.1.0 (requires `--audit`), SPDX 2.3, or CycloneDX 1.6, and `-o format=path` writes several formats in one run while still printing the normal report.", + "answer": "The default output is a human-readable text report. With `--format` you can switch to `json`, `markdown`, SARIF 2.1.0 (requires `--audit`), SPDX 2.3, or CycloneDX 1.7, and `-o format=path` writes several formats in one run while still printing the normal report.", "links": [ { "label": "Output formats", diff --git a/docs/manifest.json b/docs/manifest.json index 0b08bab2..dbcb3e08 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -73,7 +73,7 @@ { "slug": "sbom", "title": "SBOM formats", - "description": "SPDX 2.3 vs. CycloneDX 1.6, when to pick which, ingest and conversion recipes.", + "description": "SPDX 2.3 vs. CycloneDX 1.7, when to pick which, ingest and conversion recipes.", "group": "start" }, { diff --git a/internal/benchmark/run.go b/internal/benchmark/run.go index 9130d3bc..5f340daf 100644 --- a/internal/benchmark/run.go +++ b/internal/benchmark/run.go @@ -476,7 +476,7 @@ func bomlySBOMTarget(format string) (sbom.Target, error) { case "spdx": return sbom.TargetSPDX23JSON, nil case "cyclonedx": - return sbom.TargetCycloneDX16JSON, nil + return sbom.TargetCycloneDX17JSON, nil default: return "", fmt.Errorf("unsupported Bomly benchmark SBOM format %q", format) } diff --git a/internal/cli/render/sbom.go b/internal/cli/render/sbom.go index 1f56374d..1f44e6f5 100644 --- a/internal/cli/render/sbom.go +++ b/internal/cli/render/sbom.go @@ -41,7 +41,7 @@ func SBOMTarget(format output.Format) (sbom.Target, bool) { case output.FormatSPDX: return sbom.TargetSPDX23JSON, true case output.FormatCycloneDX: - return sbom.TargetCycloneDX16JSON, true + return sbom.TargetCycloneDX17JSON, true default: return "", false } diff --git a/internal/cli/render/sbom_test.go b/internal/cli/render/sbom_test.go index 7032996e..477748bd 100644 --- a/internal/cli/render/sbom_test.go +++ b/internal/cli/render/sbom_test.go @@ -24,8 +24,8 @@ func TestParseOutputFormatAcceptsSharedFormatsAndAliases(t *testing.T) { {value: "sarif", wantFormat: output.FormatSARIF, wantLabel: "sarif"}, {value: "spdx", wantFormat: output.FormatSPDX, wantTarget: sbom.TargetSPDX23JSON, wantLabel: "spdx"}, {value: "spdx-json", wantFormat: output.FormatSPDX, wantTarget: sbom.TargetSPDX23JSON, wantLabel: "spdx"}, - {value: "cyclonedx", wantFormat: output.FormatCycloneDX, wantTarget: sbom.TargetCycloneDX16JSON, wantLabel: "cyclonedx"}, - {value: "cyclonedx-json", wantFormat: output.FormatCycloneDX, wantTarget: sbom.TargetCycloneDX16JSON, wantLabel: "cyclonedx"}, + {value: "cyclonedx", wantFormat: output.FormatCycloneDX, wantTarget: sbom.TargetCycloneDX17JSON, wantLabel: "cyclonedx"}, + {value: "cyclonedx-json", wantFormat: output.FormatCycloneDX, wantTarget: sbom.TargetCycloneDX17JSON, wantLabel: "cyclonedx"}, } for _, tc := range tests { @@ -74,7 +74,7 @@ func TestParseOutputSpecsParsesReportAndSBOMTargets(t *testing.T) { if specs[4].Target != sbom.TargetSPDX23JSON || !specs[4].IsSBOM() { t.Fatalf("unexpected spdx spec: %#v", specs[4]) } - if specs[5].Target != sbom.TargetCycloneDX16JSON || !specs[5].IsSBOM() { + if specs[5].Target != sbom.TargetCycloneDX17JSON || !specs[5].IsSBOM() { t.Fatalf("unexpected cyclonedx spec: %#v", specs[5]) } } diff --git a/internal/output/output.go b/internal/output/output.go index 58d37cdc..6f1daa04 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -21,7 +21,7 @@ const ( FormatSARIF Format = "sarif" // FormatSPDX renders an SPDX 2.3 JSON SBOM. FormatSPDX Format = "spdx" - // FormatCycloneDX renders a CycloneDX 1.6 JSON SBOM. + // FormatCycloneDX renders a CycloneDX 1.7 JSON SBOM. FormatCycloneDX Format = "cyclonedx" ) diff --git a/internal/support/prose/detectors/sbom.md b/internal/support/prose/detectors/sbom.md index 3bd47bbd..c46d5de5 100644 --- a/internal/support/prose/detectors/sbom.md +++ b/internal/support/prose/detectors/sbom.md @@ -1,6 +1,6 @@ ## How `sbom` resolves -`sbom-detector` is a **direct ingest** — it accepts SPDX 2.3 JSON or CycloneDX 1.6 JSON as input and treats the SBOM as the source of truth for the graph. No subprocess runs, no other detector chains fire. +`sbom-detector` is a **direct ingest** — it accepts SPDX 2.3 JSON or CycloneDX 1.4–1.7 JSON as input and treats the SBOM as the source of truth for the graph. No subprocess runs, no other detector chains fire. | Step | Strategy | Command | | --- | --- | --- | @@ -59,5 +59,5 @@ bomly diff --sbom --base ./v1.0.cdx.json --head ./v1.1.cdx.json - **Relationship fidelity depends on the source SBOM.** If the SBOM was produced by a tool that emits a flat package list (no `DEPENDS_ON` / `dependencies` edges), Bomly's graph is also flat. `bomly explain` cannot show paths that aren't recorded. - **Vendor-specific extensions** (custom properties, non-standard package types) are passed through to the JSON output but are not used for policy decisions. - **SBOM ingest is exclusive** — combining `--sbom` with `--image` or `--url` is rejected with exit 4. -- **Format versions other than SPDX 2.3 JSON and CycloneDX 1.6 JSON** are rejected. SPDX 3.0 and CycloneDX 1.5 ingest are tracked for follow-up. +- **Format versions other than SPDX 2.3 JSON and CycloneDX 1.4–1.7 JSON** are rejected. SPDX 3.0 ingest is tracked for follow-up. - **Tag-Value SPDX** and **XML CycloneDX** are not currently ingested. diff --git a/internal/tools/sbomassurance/main.go b/internal/tools/sbomassurance/main.go index 6b57610b..c0131d89 100644 --- a/internal/tools/sbomassurance/main.go +++ b/internal/tools/sbomassurance/main.go @@ -140,7 +140,7 @@ func run(bomlyPath, outputDir, inputPath string) error { path string }{ {name: "spdx-2.3-json", path: filepath.Join(outputDir, "bomly.spdx.json")}, - {name: "cyclonedx-1.6-json", path: filepath.Join(outputDir, "bomly.cdx.json")}, + {name: "cyclonedx-1.7-json", path: filepath.Join(outputDir, "bomly.cdx.json")}, } for _, format := range formats { cliFormat := "spdx" @@ -164,7 +164,7 @@ func run(bomlyPath, outputDir, inputPath string) error { commands := [][]string{ {"java", "-jar", spdxJar, "Verify", formats[0].path}, - {cdxBinary, "validate", "--input-file", formats[1].path, "--input-format", "json", "--input-version", "v1_6", "--fail-on-errors"}, + {cdxBinary, "validate", "--input-file", formats[1].path, "--input-format", "json", "--input-version", "v1_7", "--fail-on-errors"}, } for _, command := range commands { result, _ := execute(ctx, command[0], command[1:]...) diff --git a/test/evidence/cases.json b/test/evidence/cases.json index 093e191c..7f139f5f 100644 --- a/test/evidence/cases.json +++ b/test/evidence/cases.json @@ -856,7 +856,7 @@ { "kind": "workflow", "location": ".github/workflows/sbom-interoperability.yml", - "sha256": "5434c3b2971034416cacbde5788320d47f3c1e976bdfdc60ff1f077b2e16ef09" + "sha256": "3d379300ab8e7bc02f6859c088ba06153fcbd2dc3cf159a62d890d1abda24f1a" } ], "required_tools": [ @@ -875,7 +875,7 @@ "evidence": [ { "path": ".github/workflows/sbom-interoperability.yml", - "sha256": "5434c3b2971034416cacbde5788320d47f3c1e976bdfdc60ff1f077b2e16ef09" + "sha256": "3d379300ab8e7bc02f6859c088ba06153fcbd2dc3cf159a62d890d1abda24f1a" } ], "proves": [