Skip to content

CI: add CycloneDX SBOM generation for releases - #7698

Draft
neteler wants to merge 2 commits into
OSGeo:mainfrom
neteler:ci_sbom_cyclonedx
Draft

neteler wants to merge 2 commits into
OSGeo:mainfrom
neteler:ci_sbom_cyclonedx

Conversation

@neteler

@neteler neteler commented Jul 14, 2026 •

Copy link
Copy Markdown
Member

Background: A software bill of materials (SBOM) declares the inventory of components used to build a software artifact, including any open source and proprietary software components (source: Wikipedia)

Generate a CycloneDX Software Bill of Materials (SBOM) automatically in CI for every tagged release, attached as grass.cyclonedx.json alongside the tarballs in the release draft.

A standalone sbom.yml workflow allows ad-hoc generation via workflow_dispatch. Local generation is available with make sbom (requires Syft).

The resulting JSON can be visualized in a SBOM analyser tool (e.g. sbom-analyzer)

Addresses #7215

Written with support by opencode (DeepSeek V4 Flash Free via OpenCode Zen)

Generate a CycloneDX Software Bill of Materials (SBOM) automatically in CI for every tagged release, attached as `grass.cyclonedx.json` alongside the tarballs in the release draft.

A standalone `sbom.yml` workflow allows ad-hoc generation via `workflow_dispatch`.
Local generation is available with `make sbom` (requires [Syft](https://github.com/anchore/syft)).

Addresses OSGeo#7215
@neteler neteler added this to the 8.6.0 milestone Jul 14, 2026
@neteler
neteler requested a review from cwhite911 July 14, 2026 13:52
@neteler neteler added the CI Continuous integration label Jul 14, 2026
@github-actions github-actions Bot added docs markdown Related to markdown, markdown files labels Jul 14, 2026
md5sum "${GRASS}.tar.xz" > "${GRASS}.tar.xz.md5"
sha256sum "${GRASS}.tar.xz" > "${GRASS}.tar.xz.sha256"
- name: Generate CycloneDX SBOM (for tags only)
if: startsWith(github.ref, 'refs/tags/')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@neteler do you want to try triggering this with a release draft?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to check stuff already at RC level?
(I am no CI expert)

@wenzeslaus wenzeslaus left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is my AI-boosted analysis:

Locally generated SBOM from a clean checkout of this branch with Syft
1.46.0 (same invocation as the workflow) contains 111 components: 71
GitHub Actions parsed from .github/workflows/, 11 Python packages from
man/mkdocs/requirements.txt and python/grass/docs/requirements.txt
(the mkdocs and sphinx documentation toolchains), and file entries.
binder/requirements.txt is not included because Syft skips unpinned
requirements by default.

None of GRASS's dependencies appear: the C libraries (GDAL, PROJ, GEOS,
...) and the Python runtime dependencies (numpy, ...) are not declared
in any format Syft reads. REQUIREMENTS.md is prose, and Syft ignores
[project.dependencies] in pyproject.toml even when pinned (tested);
it only catalogs pinned requirements.txt entries and lockfiles. The
vendored sources in lib/external/ (ccmath, parson, shapelib) are also
not detected, and those are exactly what a source SBOM should list for
CVE tracking. Syft cannot derive dependencies from Dockerfiles (it scans
built images) or from CMake/configure (dependencies are discovered on
the build host, not declared with versions).

The existing Docker image SBOMs (sbom: true in the Docker workflow)
are only partially better: they accurately list OS packages and
pip-installed Python packages, but not components compiled from source
inside the Dockerfiles. The alpine image installs the geospatial stack
via apk, so its SBOM covers it; the debian image builds only PDAL from
source; the ubuntu image (the basis of the latest tag) builds PROJ,
GEOS, GDAL, and PDAL from source, so its SBOM misses the entire core
stack. GRASS itself and the gdal-grass plugin are compiled from source
in all images, so no image SBOM lists GRASS or its version as a
component.

A separate issue is that the release SBOM mismatches the artifact it
accompanies: the tarball is created with --exclude=".gi*", so
.github is not in the release, yet 71 of the 111 components come from
it. Fixing this does not add the missing dependencies, it only removes
the CI tooling from the SBOM. Two options: scan the tarball itself
(file: input; Syft reads tar archives), which describes exactly the
released artifact but works only on tag runs where the tarball exists,
or keep the directory scan and exclude .github/.git via a committed
Syft config file, which works on every run (verified: 13 components
remain, matching the tarball contents).

As it stands, the published SBOM inventories the CI and documentation
toolchain rather than GRASS. Options: (a) a curated CycloneDX fragment
for lib/external/ and the C and Python dependencies, merged into the
generated file — inventory and licensing value only, since a source
release has no resolved versions, and a hand-maintained fragment can
drift from configure/CMake unless a check keeps it in sync; (b) reframe
the PR around the Docker images, where resolved versions exist and SBOMs
are already generated — the work then shifts to the source-built
components in the Dockerfiles (installing them from OS packages where
available, or declaring them separately), with the alpine image already
in good shape and the ubuntu image needing the most; (c) publish the
source SBOM as-is with the .github exclusion and cautious wording in
SECURITY.md. Option (c) is the easiest but does not address the critique
above: the published file would still present about a dozen
documentation-toolchain packages as the complete component inventory of
a GRASS release. SBOMs are consumed by tools and pipelines that never
see the SECURITY.md caveats, so a scanner reading the file would
conclude that a GRASS release contains no C libraries, no Python runtime
dependencies, and no vendored third-party code — a misleadingly clean
result for vulnerability or license scanning.

A detail: this repo squash-merges, so the PR title becomes the commit
message — per the commit message convention it should be capitalized
after the prefix: CI: Add CycloneDX SBOM generation for releases.

Comment on lines +5 to +14
workflow_dispatch:
inputs:
source-path:
description: Path to scan for SBOM generation
required: false
default: '.'
output-file:
description: Output SBOM file name
required: false
default: 'grass.cyclonedx.json'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the release workflow generating the SBOM (and uploading it as a workflow artifact on check runs, if the suggestion there is applied), the remaining use of this workflow would be ad-hoc generation for an arbitrary ref — but workflow_dispatch runs the workflow file from the selected ref, so branches without this file cannot be dispatched anyway. Suggest dropping this file (and the corresponding SECURITY.md bullet) in favor of the release step and make sbom.

If it is kept, the inputs add little: the workflow scans a fresh checkout, so source-path would only produce a partial SBOM of a subdirectory, a case better served by running Syft locally. And output-file has no observable effect here — nothing in this workflow reads the written file; the only retrievable result is the uploaded artifact, whose file name comes from artifact-name, not output-file.

Suggested change
workflow_dispatch:
inputs:
source-path:
description: Path to scan for SBOM generation
required: false
default: '.'
output-file:
description: Output SBOM file name
required: false
default: 'grass.cyclonedx.json'
workflow_dispatch:

Comment thread .github/workflows/sbom.yml Resolved
Comment thread Makefile Resolved
Comment thread Makefile
-find . -type f \( -name "*.gcda" -o -name "*.gcno" -o -name "*.gcov" \) -delete
-rm -f .coverage

sbom:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On main, Autotools and CMake are kept equivalent, so a CMake counterpart is needed. Tested locally (CMake configure and cmake --build build --target sbom with Syft 1.46.0): the SBOM is written to the build directory, which keeps the source tree clean and needs no cleaning rules on the CMake side. At the end of the top-level CMakeLists.txt:

 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/GRASSConfig.cmake
               ${CMAKE_CURRENT_BINARY_DIR}/GRASSConfigVersion.cmake
         DESTINATION ${GRASS_INSTALL_CMAKECONFDIR}/GRASS)
 
+find_program(SYFT syft)
+if(SYFT)
+  add_custom_target(
+    sbom
+    COMMAND ${SYFT} dir:${CMAKE_SOURCE_DIR} -o
+            cyclonedx-json=${CMAKE_BINARY_DIR}/grass.cdx.json
+    COMMENT "Generating CycloneDX SBOM using Syft")
+  set_target_properties(sbom PROPERTIES FOLDER Misc)
+endif()
+
 set_property(GLOBAL PROPERTY USE_FOLDERS YES)

Comment thread Makefile Resolved
Comment thread SECURITY.md Resolved
Comment thread SECURITY.md
Comment on lines +64 to +66
- For Docker images, an SBOM is embedded at build time (`sbom: true` in
`docker/build-push-action`) and attestations are published via
`actions/attest-build-provenance`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Docker workflow uses actions/attest, not actions/attest-build-provenance (only a stale permissions comment mentions the latter). The workflow details are visible in the CI files and go stale when they change; readers here need the facts, not the mechanism:

Suggested change
- For Docker images, an SBOM is embedded at build time (`sbom: true` in
`docker/build-push-action`) and attestations are published via
`actions/attest-build-provenance`.
- For Docker images, an SBOM is embedded in the image at build time and
build attestations are published.

Comment thread SECURITY.md
Comment on lines +67 to +68
- To generate an SBOM locally, install Syft and run `make sbom` from the
repository root.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the CMake target from the Makefile comment is added:

Suggested change
- To generate an SBOM locally, install Syft and run `make sbom` from the
repository root.
- To generate an SBOM locally, install Syft and run `make sbom` from the
repository root, or build the `sbom` target with CMake
(`cmake --build <build dir> --target sbom`).

Co-authored-by: Vaclav Petras <wenzeslaus@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI Continuous integration docs markdown Related to markdown, markdown files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants