Skip to content

Commit 352eabd

Browse files
paradoxboundclaude
andauthored
ci: generate SBOM and attach to GitHub Releases (OSPS-QA-02.02) (#77)
## Summary - Adds `anchore/sbom-action` to the `merge` job to generate an SPDX JSON SBOM for each Docker image release - SBOM is uploaded as `sbom.spdx.json` to the corresponding GitHub Release via `gh release upload` - Adds a smoke test in `pre-merge-cd-check` that validates SBOM generation against the PR image before merge - Bumps version to 2.6.1 ## Motivation Satisfies [OSPS-QA-02.02](https://baseline.openssf.org/versions/2025-02-25#osps-qa-0202): all compiled release assets must be delivered with a software bill of materials. ## How it works After the multi-arch manifest is created and verified in the `merge` job: 1. `anchore/sbom-action` scans the verified image and writes `sbom.spdx.json` (`upload-artifact: false`, `upload-release-assets: false` to suppress auto-upload) 2. `gh release upload --clobber` attaches the file to the GitHub Release after it is created In `pre-merge-cd-check`, a smoke test generates an SBOM from the PR amd64 image and confirms the file is non-empty, validating the action before it reaches the release pipeline. ## Test plan - [ ] `pre-merge-cd-check` passes — SBOM smoke test generates a non-empty `sbom-pr.spdx.json` - [ ] After merge, pipeline releases v2.6.1 with `sbom.spdx.json` attached to the GitHub Release - [ ] `gh release download v2.6.1 --pattern 'sbom.spdx.json'` succeeds 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Paradoxbound <paradoxbound@users.noreply.github.com> Co-authored-by: Paradoxbound <paradoxbound@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9d4e443 commit 352eabd

5 files changed

Lines changed: 56 additions & 7 deletions

File tree

.github/workflows/docker-publish.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,17 @@ jobs:
358358
run: |
359359
docker buildx imagetools inspect "${{ steps.tags.outputs.semver }}"
360360
361+
# Generate a Software Bill of Materials (SBOM) for the released image.
362+
# The SBOM is written to sbom.spdx.json and uploaded to the GitHub Release.
363+
- name: Generate SBOM
364+
uses: anchore/sbom-action@17ae1740179002c89186b61233e0f892c3118b11 # v0.23.0
365+
with:
366+
image: ${{ steps.tags.outputs.semver }}
367+
format: spdx-json
368+
output-file: sbom.spdx.json
369+
upload-artifact: false
370+
upload-release-assets: false
371+
361372
# Create the git tag only after the registry is confirmed healthy.
362373
# Idempotent: skips if the tag already exists (e.g. workflow re-run).
363374
- name: Create git tag
@@ -395,6 +406,10 @@ jobs:
395406
echo "Created GitHub Release ${GIT_TAG}"
396407
fi
397408
409+
# Upload SBOM as a release asset (--clobber is idempotent on re-runs).
410+
gh release upload "${GIT_TAG}" sbom.spdx.json --clobber
411+
echo "Uploaded SBOM to GitHub Release ${GIT_TAG}"
412+
398413
# Remove the intermediate arch-suffixed staging tags from the registry.
399414
# These are implementation artefacts and should not be publicly visible.
400415
# Uses the GHCR REST API: GET versions to find the ID, then DELETE by ID.
@@ -654,6 +669,26 @@ jobs:
654669
--cache-dir .cache/trivy \
655670
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${{ github.event.pull_request.number }}-amd64
656671
672+
# Smoke-test SBOM generation against the PR amd64 image.
673+
# Verifies anchore/sbom-action can scan and produce a valid SPDX file
674+
# before this change reaches the release pipeline.
675+
- name: Smoke test SBOM generation
676+
uses: anchore/sbom-action@17ae1740179002c89186b61233e0f892c3118b11 # v0.23.0
677+
with:
678+
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${{ github.event.pull_request.number }}-amd64
679+
format: spdx-json
680+
output-file: sbom-pr.spdx.json
681+
upload-artifact: false
682+
upload-release-assets: false
683+
684+
- name: Verify SBOM file was generated
685+
run: |
686+
if [ ! -s sbom-pr.spdx.json ]; then
687+
echo "ERROR: sbom-pr.spdx.json is missing or empty"
688+
exit 1
689+
fi
690+
echo "SBOM generated successfully ($(wc -c < sbom-pr.spdx.json) bytes)"
691+
657692
# Create a test multi-arch manifest and confirm it is pullable.
658693
- name: Create and verify PR test manifest
659694
run: |

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [2.6.1] - 2026-03-08
11+
12+
### Added
13+
- SBOM (Software Bill of Materials) in SPDX JSON format generated for each Docker image release and attached to the GitHub Release as `sbom.spdx.json` (#77)
14+
1015
## [2.6.0] - 2026-03-08
1116

1217
### Changed
@@ -111,7 +116,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
111116
- Response enhancement: URLs, content previews, human-friendly dates, word counts
112117
- LibreChat and Claude Desktop integration
113118

114-
[Unreleased]: https://github.com/paradoxbound/bookstack-mcp/compare/v2.6.0...HEAD
119+
[Unreleased]: https://github.com/paradoxbound/bookstack-mcp/compare/v2.6.1...HEAD
120+
[2.6.1]: https://github.com/paradoxbound/bookstack-mcp/compare/v2.6.0...v2.6.1
115121
[2.6.0]: https://github.com/paradoxbound/bookstack-mcp/compare/v2.5.6...v2.6.0
116122
[2.5.6]: https://github.com/paradoxbound/bookstack-mcp/compare/v2.5.4...v2.5.6
117123
[2.5.4]: https://github.com/paradoxbound/bookstack-mcp/compare/v2.5.3...v2.5.4

README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ Every Docker image published to GHCR has a [SLSA Level 2 provenance attestation]
341341

342342
```bash
343343
gh attestation verify \
344-
oci://ghcr.io/paradoxbound/bookstack-mcp:2.6.0 \
344+
oci://ghcr.io/paradoxbound/bookstack-mcp:2.6.1 \
345345
--owner paradoxbound
346346
```
347347

@@ -354,8 +354,8 @@ To verify a specific digest rather than a tag:
354354

355355
```bash
356356
# Get the digest first
357-
docker pull ghcr.io/paradoxbound/bookstack-mcp:2.6.0
358-
docker inspect ghcr.io/paradoxbound/bookstack-mcp:2.6.0 --format '{{index .RepoDigests 0}}'
357+
docker pull ghcr.io/paradoxbound/bookstack-mcp:2.6.1
358+
docker inspect ghcr.io/paradoxbound/bookstack-mcp:2.6.1 --format '{{index .RepoDigests 0}}'
359359

360360
# Verify by digest
361361
gh attestation verify \
@@ -366,7 +366,15 @@ gh attestation verify \
366366
Source releases are signed git tags — you can verify the tag signature with:
367367

368368
```bash
369-
git tag --verify v2.6.0
369+
git tag --verify v2.6.1
370+
```
371+
372+
### Software Bill of Materials (SBOM)
373+
374+
Every Docker image release includes an SBOM in SPDX JSON format, attached as an asset to the [GitHub Release](https://github.com/paradoxbound/bookstack-mcp/releases). Download it from the release page:
375+
376+
```bash
377+
gh release download v2.6.1 --pattern 'sbom.spdx.json'
370378
```
371379

372380
## Contributing

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Only the latest release is actively maintained with security updates.
66

77
| Version | Supported |
88
| ------- | --------- |
9-
| 2.6.x | yes |
9+
| 2.6.1 | yes |
1010
| < 2.6 | no |
1111

1212
## Secrets and Credentials Policy

packages/stdio/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bookstack-mcp-stdio",
3-
"version": "2.6.0",
3+
"version": "2.6.1",
44
"description": "BookStack MCP server (stdio transport)",
55
"type": "module",
66
"main": "dist/index.js",

0 commit comments

Comments
 (0)