feat: SBOM generation and OmniBOR build provenance (CRA compliance)#10343
feat: SBOM generation and OmniBOR build provenance (CRA compliance)#10343MarkAtwood wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds build-integrated supply-chain transparency outputs to the Autotools build: SBOM generation (make sbom) and OmniBOR/Bomsh build provenance tracing (make bomsh) to support CRA-style compliance workflows.
Changes:
- Add
scripts/gen-sbomPython generator for CycloneDX 1.6 + SPDX 2.3 JSON SBOM outputs (plus tag-value conversion viapyspdxtools). - Wire new
sbom,bomsh, and install/uninstall targets into Autotools (configure.ac,Makefile.am), with tool discovery viaAC_PATH_PROG. - Add end-user documentation (
doc/SBOM.md,doc/CRA.md) and reference it fromREADME.md/INSTALL.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/gen-sbom |
New Python SBOM generator producing CycloneDX/SPDX JSON with optional dependency metadata. |
Makefile.am |
Adds sbom/bomsh targets, install/uninstall rules, and clean integration. |
configure.ac |
Detects required tools (python3, pyspdxtools, git, bomtrace3, bomsh scripts) and exports needed vars. |
doc/include.am |
Ships new docs in the dist tarball; cleans generated OmniBOR directory. |
doc/SBOM.md |
Primary documentation for SBOM + OmniBOR workflows and outputs. |
doc/CRA.md |
Downstream integrator guidance for CRA-oriented SBOM/provenance usage. |
README.md |
Adds brief pointers to the new SBOM/CRA and OmniBOR features. |
INSTALL |
Adds new sections documenting make sbom / make bomsh usage and prerequisites. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Note that we are in the process of removing liblms and libxmss (see #10292), which will simplify the dependency analysis in the python script. |
|
Three CI failures to address: 1. 2. 3. PRB-master-job failure appears to be a reporting artifact (no test results found), not a real failure. |
FYI, this will be resolved with #10387 |
c6d9f94 to
39aefe7
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10343
No scan targets match the changed files in this PR. Review skipped.
|
retest this please |
MarkAtwood
left a comment
There was a problem hiding this comment.
Pre-merge self-review
Ran a structured pre-merge review across scripts/gen-sbom, scripts/test_gen_sbom.py, .github/workflows/sbom.yml, Makefile.am, and the docs. Five commits result, each listing its closed findings in the trailer:
test(sbom): expand coverage; CI hard-requires pcpp
fix(sbom): correctness fixes in gen-sbom
build(sbom): Makefile.am hygiene
ci(sbom): pin deps, harden token, partial-clone bomsh
docs(sbom): correct NTIA/SPDX claims; fix broken anchor
Real defects fixed (fix(sbom))
parse_options_hURL corruption.re.split(r'/\*|//', raw)[0]truncatedPACKAGE_URL "https://..."at the first//inside the URL, emittingPACKAGE_URL = "https:in the SBOMwolfssl:build:*properties. Replaced with a string-literal-aware state machine.detect_licensemissed the canonical FSF preamble.or\s+(any\s+)?laterfailed onor (at your option) any later version(the FSF GPLv2 preamble used in millions of upstream COPYING files); silently mis-detected asGPL-X.0-only. Broadened with a sentence-bounded filler match.- Empty
--libaccepted. A misconfigured build pointing--libat/dev/nullor an empty.asilently emitted the well-known empty-file SHA-256 (e3b0c44…b855) as the wolfSSL component checksum; both SPDX and CDX validators accepted it. Hard-fail gate on size 0 with an actionable error;--srcszero-byte placeholders get a soft warning (cross-compile setups legitimately use them).
Coverage gaps closed (test(sbom))
+42 tests covering detect_license (had zero tests), sha256_file's chunked-read path (the loop body wasn't exercised by the only existing empty-file vector), all four SBOM document generators (generate_cdx / generate_spdx / cdx_dep_component / spdx_dep_package), and the LicenseRef-without-license-text CLI rejection. Independent oracles throughout — SPDX licence list, hashlib one-shot SHA-256, CDX 1.6 / SPDX 2.3 schema field semantics.
TestParseUserSettings.setUp previously called self.skipTest on missing pcpp. The unit job didn't install pcpp, so 9 standalone-path tests silently skipped on every PR run. Now hard-fails with an actionable install hint, and the unit job installs pcpp==1.30.*.
Build hygiene (build(sbom))
Five Makefile.am improvements — none changes user-facing behaviour for any correct invocation; each closes a specific failure mode:
- bomsh artifact glob quoted for spaces in
abs_builddir SOURCE_DATE_EPOCHonly exported whengit log -1 --format=%ctreturned non-empty (silent shallow-clone case)--license-override/--license-textonly passed when set (decouples Makefile from gen-sbom's argparse default)install-bomshswitched from a two-tar pipeline (which masked source-side errors due to POSIX sh's pipeline exit-status semantics) tocp -R src/. dst/uninstall-hookdepends onuninstall-sbom+uninstall-bomshinstead of duplicating their bodies
CI hardening (ci(sbom))
pcpp==1.30.* pinned, permissions: contents: read at workflow level (defence-in-depth — workflow does no API writes), git clone --filter=blob:none for the bomsh fetch (saves ~10s per run; pinned SHA makes blob filter safe).
Doc corrections (docs(sbom))
README.mdpreviously said "Both produce SPDX 2.3 + CycloneDX 1.6 JSON validated against NTIA minimum elements". Neither path runs an NTIA-minimum-elements checker. Onlymake sbomrunspyspdxtools(SPDX-spec, not NTIA-specific). Corrected.doc/CRA.mdnamedntia-conformance-checkeras enforcing SPDX §10.1hasExtractedLicensingInfos; it does not (that is pyspdxtools' job). Corrected with explicit text on whatntia-conformance-checkeractually validates so a reader cannot be misled into running a malformed LicenseRef SBOM through the wrong tool and getting a false pass.doc/SBOM.mdanchor link#3-make-bomshresolved to the wrong section; fixed to the GFM auto-anchor for §3.1.
Re the Copilot inline review
The Copilot review raised 8 concerns. 7 are addressed: the NOASSERTION CDX wording in SBOM.md §2.4, make bomsh → make sbom rebuild ordering, hardcoded library paths replaced with the WOLFSSL_LIB_DSO_BASENAMES glob, scripts/gen-sbom added to EXTRA_DIST, trap-based staging cleanup, and removing the hardcoded licenseListVersion.
The remaining concern — install-bomsh using a non-INSTALL_DATA copy mechanism — is unchanged in intent (this round uses cp -R rather than the previous tar pipeline for clean error propagation but did not adopt find ... | xargs $(INSTALL_DATA)). The OmniBOR tree is a directory of content-addressed binary objects; using $(INSTALL_DATA) per-file is the more automakey approach but adds complexity for a directory whose layout is meant to be preserved verbatim. Open to revisiting if maintainers prefer.
Acknowledgement
@sameehj — the standalone --user-settings / --srcs entry point, SPDX 2.3 LicenseRef compliance, library-discovery glob, and CI workflow shape this PR from "works on my machine" into a production-ready, embedded-customer-friendly tool. The test infrastructure you built was solid. Thanks.
cc @Frauschi for visibility — make clean removes both the doxygen artefacts and BOMSH_OMNIBORDIR via doc/include.am's clean-local:.
525918d to
cf0bdd9
Compare
|
retest this please |
|
retest this please |
578fbee to
9d49a5d
Compare
dgarske
left a comment
There was a problem hiding this comment.
Skoll Code Review
Scan type: reviewOverall recommendation: COMMENT
Findings: 4 total — 4 posted, 0 skipped
3 finding(s) posted as inline comments (see file-level comments below)
Posted findings
- [Medium] make sbom does not thread --dep-version; liboqs/zlib version becomes NOASSERTION without pkg-config —
Makefile.am:409-475 (sbom target) - [Low] Redundant/dead branch logic in FIPS product bucket computation —
scripts/gen-advisory:product_model (FIPS block) - [Low] Duplicate pkg-config invocation per dependency —
scripts/gen-sbom:cdx_dep_component / spdx_dep_package - [Info] Executable-bit inconsistency between gen-sbom (755) and gen-advisory (644) —
scripts/gen-advisory:1
Review generated by Skoll
|
Good catch on the --dep-version gap. Yes, let's expose a SBOM_DEP_VERSIONS make variable so autotools packagers without pkg-config can still record dep versions. @sameehj can you pick this up along with the other Skoll findings? |
7c6dc94 to
25e9bd8
Compare
|
retest this please |
MarkAtwood
left a comment
There was a problem hiding this comment.
Code Review
Blockers
1. gen-advisory: resolved → known_affected mapping inversion
_STATE_TO_BUCKET maps resolved to known_affected. This is a semantic inversion — patched products get reported as actively vulnerable in the CSAF output. Downstream scanners will tell operators to patch what's already fixed. Should be 'resolved': 'fixed'. Same gap for resolved_with_pedigree which is absent from the mapping entirely and falls through to the known_affected default.
2. gen-advisory: unmapped states default to known_affected
bucket = _STATE_TO_BUCKET.get(state, 'known_affected')false_positive and any other unmapped CycloneDX analysis state silently becomes "actively vulnerable." Either map them correctly (e.g. false_positive → known_not_affected) or raise an error on unknown states rather than defaulting to the worst-case bucket.
3. Makefile.am: uninstall-advisory missing from uninstall-hook
uninstall-hook: uninstall-sbom uninstall-bomshuninstall-advisory is absent. make install-advisory && make uninstall leaves advisory files behind.
4. Makefile.am: install-advisory glob loop breaks on empty match
@for f in $(ADVISORY_OUT_DIR)/*.json; do \
$(INSTALL_DATA) "$$f" $(DESTDIR)$(advisorydir)/; \
doneOn POSIX sh (without nullglob), when the glob matches nothing the loop body executes with the literal glob string, calling $(INSTALL_DATA) on a nonexistent file. Add [ -f "$$f" ] || continue as the first statement in the loop body.
High
5. CVSS scoring errors in CVE records
Both CVE records use privilegesRequired: LOW. Neither vulnerability requires system account access — obtaining a TLS cert (5501) or sending network traffic (5778) are PR:N scenarios. CVE-2026-5778 also uses userInteraction: PASSIVE for a sniffer crash triggered by network traffic with no human involvement — should be UI:N. These errors understate the severity scores.
6. gen-sbom TOCTOU in gitoid_blob_sha256
os.path.getsize(path) is called before open(path, 'rb'). If the file changes between the two calls, the gitoid header encodes the wrong size. Fix: use os.fstat(f.fileno()).st_size after opening the file descriptor (same approach git itself uses).
7. bomsh_verify.py path traversal via unsanitized gitoid
The gitoid string extracted from the SPDX document is used directly in os.path.join(omnibor_objects_dir, gid[:2], gid[2:]) without validating that it is a 40-character hex string. A crafted SPDX file with gitoid:blob:sha1:../../../etc/shadow passes the prefix check and enables path traversal. Impact is read-only (os.path.isfile), but still an info-disclosure risk when SPDX comes from untrusted build artifacts. Fix: re.fullmatch(r'[0-9a-f]{40}', gid) before path construction.
8. EXTRA_DIST hardcodes CVE filenames
EXTRA_DIST += advisories/records/CVE-2026-5501.json \
advisories/records/CVE-2026-5778.jsonEvery new CVE record requires a manual Makefile.am edit or it's silently missing from make dist tarballs. The failure is silent — make dist succeeds, downstream make advisory fails. Use a dist-hook glob instead.
Medium
9. CI actions pinned to tags, not SHAs
All uses: lines reference floating tags (actions/checkout@v4, etc.). For a supply-chain provenance workflow this is the most visible inconsistency — the SBOM workflow's own build inputs are mutable. The bomsh clone correctly uses a full commit SHA; actions and the liboqs/strace clones should follow the same pattern.
10. CSAF category: 'self' with a generic landing page URL
'references': [{'summary': '...', 'url': WOLFSSL_ADVISORIES_URL, 'category': 'self'}]CSAF 2.0 mandatory test 6.1.20 requires the self reference to point to the canonical location of this specific document, not a generic advisories page. This will fail conformance validation when the document is published.
11. VEX overlay schema doesn't require state
The overlayEntry object has no required array, so the core VEX determination field (state) is optional. An empty overlay entry {} validates silently against the schema. Add "required": ["state"] to the overlayEntry definition.
|
retest this please |
Won't change, by design. The bucket applies to the CVE's vulnerable version ranges, which stay known_affected permanently; the patched release is emitted separately into fixed in the per-product loop. Mapping resolved → fixed would mislabel the old vulnerable ranges as patched. Added resolved_with_pedigree to the explicit map (the real gap). Documented in-code.
Fixed
Fixed
Fixed
Fixed
Fixed
Fixed
Fixed
Fixed
Fixed
|
|
retest this please |
|
Holding merge until after the current release — wolfSSL/wolfssl is in code freeze. Will pick this up once master reopens. |
dgarske
left a comment
There was a problem hiding this comment.
Skoll Code Review
Scan type: reviewOverall recommendation: COMMENT
Findings: 2 total — 2 posted, 0 skipped
2 finding(s) posted as inline comments (see file-level comments below)
Posted findings
- [Medium] bomsh recipe shell block lacks set -e; a failed nested make sbom is masked —
Makefile.am:698-720 - [Low] make advisory / install-advisory targets undocumented in INSTALL and README —
Makefile.am:598-644
Review generated by Skoll
|
retest this please |
|
@cconlon — one focused ask: please review this PR (#10343) only. It's the keystone of the SBOM / EU CRA work and the one piece with real logic to look at — the The rest of the rollout does not need your eyes — flagging only so you know it's out there, not asking you to review it:
Happy to walk through #10343 whenever. |
Add tooling to produce Software Bills of Materials and build provenance for wolfSSL, supporting EU Cyber Resilience Act (CRA) obligations. SBOM generation: - New `make sbom` target producing SPDX 2.3 output with NTIA minimum elements, urn:uuid document namespaces, and SPDX LicenseRef compliance. - Reproducible library discovery across autotools and CMake builds, with liboqs recorded as a linked artefact. - Standalone `scripts/gen-sbom` for embedded / RTOS / custom-builder flows that do not use the main build system, plus --srcs-file, --no-artifact-hash, and hash-source options. Build provenance (OmniBOR / bomsh): - End-to-end bomsh tracing of the built binaries with ArtifactID insertion, snapshotting the traced library before libtool relink and hashing the bomsh-traced binary. - `scripts/bomsh_verify.py` to validate provenance against the traced gitoid. Security advisories: - `scripts/gen-advisory` generating CSAF 2.0 and CycloneDX VEX, with a `make` target, VEX overlay schema/example, and CWE name data. Docs, tests, and CI: - doc/SBOM.md and doc/CRA.md, plus README/INSTALL updates. - Unit and regression tests for gen-sbom and gen-advisory. - New sbom.yml and advisory.yml workflows: SPDX validation via pyspdxtools, CSAF validation, bomsh provenance verification, SBOM artifact archiving, macOS coverage, and actions pinned to SHAs. Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Summary
Adds two complementary supply chain transparency targets to the wolfSSL autotools build:
make sbom— generates a Software Bill of Materials (SPDX 2.3 + CycloneDX 1.6) for EU Cyber Resilience Act (CRA) compliancemake bomsh— generates an OmniBOR artifact dependency graph (cryptographic source-to-binary traceability) using the Bomsh projectmake bomshenriches the SPDX document with aPERSISTENT-ID gitoidExternalRef, bridging component identity and build provenance in a single filemake sbom
Produces three files:
wolfssl-<version>.cdx.jsonwolfssl-<version>.spdx.jsonwolfssl-<version>.spdxSBOM contents: name, version, supplier, license (parsed from
LICENSINGat generation time), copyright, SHA-256 of installed library, CPE, PURL, download location, build configuration defines, and optional third-party dependency versions (liboqs, libz, libxmss, liblms).Implementation:
scripts/gen-sbom(Python 3, stdlib only) stages amake installinto a temp directory, hashes the installed library, generates both formats, removes staging.configure.acdetectspython3,pyspdxtools, andgitviaAC_PATH_PROG.make sbomfails with a clear error if either required tool is missing.make bomsh
Runs a full clean rebuild under
bomtrace3(patched strace — userspace only, no kernel modifications) to produce an OmniBOR artifact graph inomnibor/. Ifbomsh_sbom.pyis available andwolfssl-<version>.spdx.jsonexists, annotates the SPDX with aPERSISTENT-ID gitoidExternalRef.configure.acdetectsbomtrace3,bomsh_create_bom.py, andbomsh_sbom.pyviaAC_PATH_PROG. The raw logfile and conf file are written to the build directory (not/tmp/) to avoid concurrent-build collisions. All generated files removed bymake clean.Install targets
Documentation
doc/SBOM.md— unified reference covering both targets, combined workflow, all output files, implementation notesdoc/CRA.md— product-integrator guide: how to incorporate wolfSSL's SBOM artefacts into a downstream product SBOM (SPDXExternalDocumentRef, CycloneDX component reference), commercial license guidance, OmniBOR gitoid meaning, auditor handoff checklist, links to OpenSSF CRA and SBOM Everywhere SIG guidanceINSTALL: sections 21 and 22README.md: brief SBOM/CRA and OmniBOR/Bomsh sectionsdoc/include.am:SBOM.mdandCRA.mdadded todist_doc_DATATest plan
./autogen.sh && ./configure && make && make sbom— verify three SBOM files produced and SPDX validatesmake install-sbom/make uninstall-sbom— verify files install and remove cleanlymake clean— verify all generated files removedbomtrace3andbomsh_create_bom.pyin PATH:make sbom && make bomsh— verifyomnibor/produced andomnibor.wolfssl-<ver>.spdx.jsoncontainsPERSISTENT-ID gitoidExternalRefbomtrace3in PATH:make bomsh— verify clear error message, non-zero exitmake sbom:make bomsh— verify OmniBOR graph produced, NOTE printed about missing SPDX, no failure