feat: add make sbom target - #1050
Conversation
Adds sbom, install-sbom, and uninstall-sbom targets. Runs gen-sbom to produce CDX and SPDX outputs. Requires WOLFSSL_DIR pointing to a wolfssl tree with the feat/sbom-embedded branch (includes gen-sbom).
There was a problem hiding this comment.
Pull request overview
Adds SBOM generation and install/uninstall targets to the autotools build, intended to produce CycloneDX + SPDX outputs and validate SPDX on the build host.
Changes:
- Add
make sbom,make install-sbom, andmake uninstall-sbomtargets plus related variables/cleanup inMakefile.am. - Extend
configure.acto discoverpython3/pyspdxtoolsand to break out libtool version-info components into separate substituted variables.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| Makefile.am | Adds SBOM generation/validation targets and output staging/cleanup rules. |
| configure.ac | Adds tool discovery for SBOM prerequisites and refactors libtool version-info variables. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Include AM_CPPFLAGS and config.h in the options snapshot so the configured feature set (WOLFSSH_SCP/SFTP/NO_WOLFSSH_SERVER/...) appears in the SBOM instead of being empty. - Discover static (.a), Mach-O (.dylib) and Windows (.dll/.lib) artifacts, not just a versioned .so. - Set SOURCE_DATE_EPOCH from the last git commit for reproducible output. - Clean staging tree and temp defines file via trap even on failure. - Record wolfssl as an SBOM dependency (--dep-wolfssl), version auto-detected from WOLFSSL_DIR/wolfssl/version.h. - configure.ac: detect git; drop unused version-info split + comment. Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
There was a problem hiding this comment.
Tested make sbom end-to-end on macOS (--enable-all, gen-sbom from wolfssl feat/sbom-embedded, spdx-tools + pcpp installed): exits 0, all three outputs produced, the Mach-O .dylib discovery branch fires, the trap cleans up the staging tree and temp defines file, the configured feature macros (WOLFSSH_SFTP/SCP/CERTS/AGENT/SSHD, NO_INLINE, NDEBUG) now appear in the SBOM, and pyspdxtools re-validation passes clean. Two follow-ups below, none blocking.
Default SBOM_LICENSE_OVERRIDE to GPL-3.0-only so the SBOM records the correct license instead of NOASSERTION, and point the --dep-wolfssl note at wolfSSL/wolfssl#10343. Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Move the make sbom / install-sbom / uninstall-sbom recipe out of Makefile.am into a reusable scripts/sbom.am fragment; Makefile.am now just declares the product facts (name, LICENSING, wolfssl dependency, GPL-3.0-only default) and includes it. Add a GitHub Actions workflow covering both SBOM paths: - autotools: build wolfSSL + wolfSSH, run make sbom, assert SPDX validity, CycloneDX identity, reproducibility, and the wolfssl dependency edge. - embedded: run gen-sbom against user_settings.h (via pcpp) + the wolfSSH source set, asserting the same identity/validity/reproducibility plus that user_settings.h options and the wolfssl dependency are captured. Both gracefully skip on a gen-sbom that predates the wolfSSL SBOM change. Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Guard the SBOM_GEN wolfSSL fallback with $(if $(WOLFSSL_DIR),...) so an unset WOLFSSL_DIR leaves it empty (yielding the intended "set WOLFSSL_DIR" error) instead of resolving to an absolute /scripts/gen-sbom that could run an unrelated host script. Reword the fragment header to describe the actual gen-sbom resolution (vendored copy if present, else WOLFSSL_DIR) rather than claiming offline tarball builds that aren't wired up yet. Condition the README's wolfSSL-dependency statement on a sufficiently new gen-sbom to match the existing degradation note. Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
aidangarske
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: REQUEST_CHANGES
Findings: 2 total — 2 posted, 0 skipped
Posted findings
- [High] Default SBOM license contradicts source license headers —
Makefile.am:98 - [Medium] SBOM workflow can pass without exercising the new target —
.github/workflows/sbom.yml:64-79
Review generated by Skoll.
Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
jackctj117
left a comment
There was a problem hiding this comment.
Skoll Multi-Scan Review
Modes: reviewOverall recommendation: COMMENT
Findings: 5 total — 5 posted, 0 skipped
4 finding(s) posted as inline comments (see file-level comments below)
1 finding(s) not tied to a diff line (full detail below)
One or more scans did not complete — the results below are partial.
Posted findings
- [Medium] [review] CI workflow pins wolfSSL to an unmerged PR ref for all events, including every pull_request —
.github/workflows/sbom.yml:3-13, 41-46 - [Low] [review] wolfssl version.h parse uses [ \t] which does not match a literal tab —
scripts/sbom.am:144-145 - [Low] [review] sbomdir hardcodes
$(datadir)/doc/$ (PACKAGE) instead of using $(docdir) —scripts/sbom.am:55 - [Low] [review] uninstall-hook removes SBOM files that plain make install never installs —
scripts/sbom.am:185-196
Findings not tied to a diff line
SBOM targets silently require GNU make; ?= and GNU-only make functions are undocumented
File: scripts/sbom.am:46-63, Makefile.am:98
Function: sbom
Severity: Medium
Category: question
The fragment relies on GNU make extensions: conditional assignment ?= (SBOM_ARTIFACT ?= lib, SBOM_LICENSE_OVERRIDE ?= GPL-3.0-or-later) and the GNU-only functions $(wildcard ...), $(if ...), $(firstword ...), and $(addprefix ...). ?= is not one of Automake's documented assignment operators (=, +=, :=); confirm autoreconf accepts it without error. Note CI only runs autoreconf/configure on the wolfSSH tree when gen-sbom is detected (the steps.gate.outputs.have == 'yes' gate), so a hard Automake failure would only surface when CI checks out a wolfssl ref carrying the script. Under a non-GNU make (e.g. BSD make), SBOM_GEN/SBOM_LIB_GLOBS evaluate to empty and make sbom fails with a misleading 'gen-sbom not found' / 'no artifact found' error rather than a clear 'GNU make required' message.
Recommendation: Confirm autoreconf accepts ?= on the toolchain versions you support, and document in the README/comment that the SBOM targets require GNU make. Optionally guard with a clearer error when GNU-make features are unavailable.
Referenced code: scripts/sbom.am:46-63, Makefile.am:98-100 (3 lines)
Review generated by Skoll
Sync the vendored fragment with the canonical wolfSSL copy: add $(AM_CFLAGS) $(CFLAGS) to the -dM -E build-options capture and make the config header path overridable via SBOM_CONFIG_H (default config.h) so the SBOM records the configured feature macros. Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Re-sync scripts/sbom.am with the canonical wolfSSL copy ($(docdir) sbomdir, [[:space:]] version parse, GNU-make and uninstall-sbom docs) and widen the SBOM workflow pull_request filter to '**'. Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
ejohnstown
left a comment
There was a problem hiding this comment.
I guess my only issue is this PR depends on wolfSSL's PR 10343 to be merged first. The GitHub Action works without the PR merged, so that's OK. But someone may see the note about the make sbom and it not work if wolfSSH is released with this without the wolfSSL merge.
Sync scripts/sbom.am with the unified canonical copy in wolfSSL (scripts/sbom.am), which adds the SBOM_OPTIONS_H override so one fragment supports both options.h- and config.h/AM_CFLAGS-based macro capture. wolfssh uses the default config.h path and does not set SBOM_OPTIONS_H, so behavior is unchanged. Keeps the vendored fragment byte-identical to the canonical. Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
|
Note on the review threads above: the recipe moved out of |
wolfSSL/wolfssl#10343 merged on 2026-07-27, but the SBOM workflow still checked out wolfssl at refs/pull/10343/head. GitHub keeps PR refs alive after merge, so CI stayed green while validating a pre-merge snapshot rather than master. Point wolfssl_ref and its fallback at master. Distinguish the two gen-sbom lookup failures. A set-but-wrong WOLFSSL_DIR previously printed the "re-run with WOLFSSL_DIR=..." advice to someone who had already set it; it now names the path that was probed and states that WOLFSSL_DIR is a source tree, not an install prefix. Refresh the README and in-recipe notes that still told users to track the #10343 branch until it merged.
The gate skipped SBOM generation and exited 0 whenever wolfssl's scripts/gen-sbom was absent, so the job could report success having never exercised `make sbom`. That was deliberate while the script was unmerged, but it landed on master in wolfSSL/wolfssl#10343. On master, a missing gen-sbom or one too old for --dep-wolfssl is a regression, so fail instead of skipping: a green run now always means the target was exercised and the wolfssl-dependency assertions ran. Only a deliberately pinned older wolfssl_ref still degrades to a skip. The ref is passed through env rather than interpolated into the shell script, to keep the workflow expression out of the command line.
Both Skoll findings are addressed. [High] default SBOM license: fixed in ccbaecc, Makefile.am:98 is now SBOM_LICENSE_OVERRIDE ?= GPL-3.0-or-later. [Medium] workflow can pass without exercising the new target: fixed in feaf692, the gate now fails on master when gen-sbom is missing or lacks --dep-wolfssl, so a green run means make sbom actually ran. Dismissing this automated review; human review still pending.
ejohnstown
left a comment
There was a problem hiding this comment.
Re-reviewed at 7e22d45a (+1 commit since my last pass: "fix: unpin SBOM CI from merged wolfssl PR ref").
Built and ran everything on macOS/arm64 (gcc-15, GNU make 3.81): --enable-all and plain ./configure against a wolfSSL 5.9.2 install, WOLFSSL_DIR pointing at a wolfssl checkout at origin/master (b83f5d60f), --prefix=/opt/wolfssh-sbom. The prefix is root-owned here, so the install was exercised through DESTDIR staging of the identical recipe.
Fixed: CI/docs unpinning
Verified master everywhere -- workflow input default, checkout ref, and both skip-notice strings -- plus the README and the sbom.am NOTE. wolfSSL master's gen-sbom is what I tested against throughout, exit 0.
The new "not an install prefix" error is a real improvement and fires correctly:
$ make sbom WOLFSSL_DIR=/opt/wolfssl
ERROR: no gen-sbom at /opt/wolfssl/scripts/gen-sbom
WOLFSSL_DIR must name a wolfSSL *source tree*, not an
install prefix such as /usr/local or /opt/wolfssl.
make: *** [sbom] Error 1
New: the vendored scripts/sbom.am has diverged from the canonical copy
That fix was made by editing scripts/sbom.am in this repo only. The file's own header says:
This is the canonical copy (wolfSSL repository, scripts/sbom.am); product repositories vendor a copy of it and must be kept in sync with this file.
sha256 of scripts/sbom.am:
wolfssl/scripts/sbom.am 282fd7ca...
wolfssh, previous head 282fd7ca... (identical)
wolfssh, this head 5118dc2e... (diverged)
Two hunks are wolfSSH-only now: the WOLFSSL_DIR-aware error block and the reworded --dep-wolfssl NOTE. A wolfssl checkout at origin/master does not carry them, and I found no open wolfSSL PR syncing them. Both changes are product-agnostic and belong upstream: land them in wolfSSL first, re-vendor here, and the file goes back to byte-identical. A CI check that diffs the vendored copy against the canonical one would stop this drifting again.
Still open
1. The recorded wolfSSL dependency version does not track the linked wolfSSL
This is the one I would hold on. SBOM_WOLFSSL_VERSION falls back to $(WOLFSSL_DIR)/wolfssl/version.h, but WOLFSSL_DIR is only there to locate gen-sbom and is a different input from --with-wolfssl. Built and linked against wolfSSL 5.9.2, with WOLFSSL_DIR pointing at a tree whose version.h says 9.9.9:
component: wolfssl 9.9.9 pkg:github/wolfSSL/wolfssl@v9.9.9
Worse, with gen-sbom present but no wolfssl/version.h under WOLFSSL_DIR, make sbom exits 0 and emits:
component: wolfssl None None
No version, no purl, no warning -- silently unscannable, which defeats the README's stated reason for the entry ("so vulnerability scanners can associate wolfSSL advisories with a wolfSSH deployment"). gen-sbom's pkg-config fallback does not rescue it, because a non-default prefix is not on the default PKG_CONFIG_PATH and the recipe does not set one.
The new error text sharpens the case: it tells users WOLFSSL_DIR is a source tree and explicitly not an install prefix -- i.e. not the thing you linked. Those are two independent inputs and only the wrong one feeds the dependency version.
Fixable entirely on the wolfSSH side through the documented SBOM_WOLFSSL_VERSION hook, so the canonical sbom.am does not need to change. I prototyped and verified this:
# configure.ac, after AC_CHECK_LIB([wolfssl],[wolfCrypt_Init],...)
# Record the version of the wolfSSL actually being linked, for the SBOM's
# dependency entry. WOLFSSL_DIR (used by `make sbom` only to locate gen-sbom)
# may be an unrelated source tree, so it must not be the version source.
AC_MSG_CHECKING([wolfSSL version for SBOM])
WOLFSSL_VERSION=
if test -f "${wcpath}/include/wolfssl/version.h"; then
WOLFSSL_VERSION=`sed -n ['s/.*LIBWOLFSSL_VERSION_STRING[ ]*"\([^"]*\)".*/\1/p'] "${wcpath}/include/wolfssl/version.h"`
fi
AS_IF([test -n "$WOLFSSL_VERSION"],
[AC_MSG_RESULT([$WOLFSSL_VERSION])],
[AC_MSG_RESULT([unknown])])
AC_SUBST([WOLFSSL_VERSION])# Makefile.am, after SBOM_DEP_WOLFSSL = yes
# Version of the linked wolfSSL, detected by configure from --with-wolfssl.
SBOM_WOLFSSL_VERSION = @WOLFSSL_VERSION@With that applied it records 5.9.2 even when WOLFSSL_DIR claims 9.9.9, and an explicit make sbom SBOM_WOLFSSL_VERSION=... still overrides.
2. The CI regression guard does not guard
assert any(n.startswith('wolfssl:build:') for n in props), \
'no wolfssl:build:* properties - options snapshot is empty'The comment says this catches the options snapshot regressing to empty -- the bug fixed by "scripts/sbom.am: capture AM_CFLAGS/CFLAGS and configurable config.h". It does not. Reconfiguring plainly (exactly as CI does) and reintroducing that regression, i.e. dropping AM_CPPFLAGS from the -dM -E capture:
BASELINE wolfssl:build:* count = 50 WOLFSSH_TERM present = True
REGRESSED wolfssl:build:* count = 48 WOLFSSH_TERM present = False
CI assertion any(startswith 'wolfssl:build:') -> True
config.h alone always supplies ~50 properties, so the assertion passes while the compiler-side capture is silently broken. Since CI configures plainly, WOLFSSH_TERM is the only marker that comes via AM_CPPFLAGS; asserting on wolfssl:build:WOLFSSH_TERM would make the guard real. (The embedded job's WOLFSSH_SFTP assertion is the strong one.)
3. Scope: the installed apps are not covered
An --enable-all install stages bin/wolfssh and bin/wolfsshd, but SBOM_ARTIFACT = lib means the only components are wolfssh (hash-source: lib) and wolfssl. sbom.am is lib-or-bin, so covering both needs a canonical change. Fine to defer, but given the CRA framing it is worth stating in the README rather than leaving implied.
4. Minor (both gen-sbom side, not this PR)
The main component is GPL-3.0-or-later while the wolfssl dependency component is hardcoded GPL-3.0-only, even though both projects' source headers carry the same "or (at your option) any later version" text. Also, properties on a wolfSSH SBOM are namespaced wolfssl:build:*.
Everything else verified green
- Reproducible across two runs (byte-identical); CDX timestamp matches
git log -1 --format=%ct --enable-allcaptures all 12WOLFSSH_*feature macros (63 properties vs 50 plain), and the library hash changes with configurationCPPFLAGS=-DDEFAULT_WINDOW_SZ=16384at configure time lands in the SBOM- VPATH build, and
--disable-shared --enable-staticcorrectly hasheslibwolfssh.a install-sbominstalls only undershare/doc/wolfssh;uninstall-hookchain removes them from a plainmake uninstall(0 files left)make cleanremoves the outputs (CLEANFILEScorrect)autogen.shwithWARNINGS=all,errorexits 0make distshipsscripts/sbom.am; full tarball unpack -> configure -> make ->make sbomexits 0pyspdxtoolsvalidates the SPDX JSON; tag-value output carriesDESCRIBESandDEPENDS_ON- The
GPL-3.0-or-lateroverride is correct:src/ssh.csays "version 3 ... or (at your option) any later version", whileLICENSINGsays only "GPLv3", whichdetect_licensemaps toGPL-3.0-only
Net: the mechanics hold up well outside the CI happy path. Findings 1 and 2 above, plus re-syncing scripts/sbom.am upstream, are what I would want before merge.
The wolfSSL dependency version came solely from WOLFSSL_DIR/wolfssl/version.h. That header is generated by configure as well as tracked, so `make distclean` in the wolfSSL tree removes it. With it gone the --dep-version override was never passed and gen-sbom fell back to `pkg-config --modversion wolfssl`, which reports the *installed* wolfSSL. On a host whose installed build differs from WOLFSSL_DIR the SBOM recorded that unrelated version (9.9.9 in the report) and still exited 0, attesting a component wolfSSH was not built against. Fall back to AC_INIT in WOLFSSL_DIR/configure.ac, which survives distclean, and fail when neither source is readable rather than letting an installed copy answer for the tree. SBOM_WOLFSSL_VERSION still overrides both.
The dependency assertion checked only that wolfssl was present with a DEPENDS_ON edge, never that the recorded version matched WOLFSSL_DIR, so the SBOM could attest a version wolfSSH was not built against and still pass. Compare versionInfo against the tree. That assertion alone would not have caught it: the runner installs wolfssl from the same tree it passes as WOLFSSL_DIR, so pkg-config and wolfssl/version.h always agree and a wrong source is indistinguishable from a right one. Add a step that forces them apart -- a stub pkg-config reports a sentinel version, and a distclean'd copy of the tree has no generated version.h, so the configure.ac fallback is the only route to a correct answer. Reverting the recipe fix makes this step fail with the sentinel, and the fallback path now has CI coverage it lacked.
The 2026-08-26 GitHub Actions major outage left this branch's checks in a mixed state: several workflows recorded startup_failure with no jobs created, and wolfSSHd Test could not be re-run because there was nothing to retry. No code change; this empty commit exists only to trigger a clean run of every workflow.
Summary
Adds
make sbom,make install-sbom, andmake uninstall-sbomtargets to the autotools build..sofor hashingcc -dM -Efor the options snapshotgen-sbom(from wolfsslscripts/gen-sbom) to produce CycloneDX and SPDX outputspyspdxtoolsUsage
```sh
./configure --with-wolfssl=/path/to/wolfssl/install
make
make sbom WOLFSSL_DIR=/path/to/wolfssl/source
```
Outputs:
wolfssh-<version>.cdx.json,wolfssh-<version>.spdx.json,wolfssh-<version>.spdxRequirements
WOLFSSL_DIRmust point to a wolfssl source tree containingscripts/gen-sbom(available on thefeat/sbom-embeddedbranch of wolfssl)python3andpip install spdx-toolson the build hostTest plan
./configure --with-wolfssl=... && make && make sbom WOLFSSL_DIR=...pyspdxtools --infile wolfssh-*.spdx.jsonpasses validation