From 13e3a02a12c5ae002b4ded8259ac8aec84cf7c2f Mon Sep 17 00:00:00 2001 From: David O'Keeffe Date: Sat, 16 May 2026 22:18:37 +1000 Subject: [PATCH] ci(release): generate signed SBOM on each release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds supply-chain provenance to every GitHub Release so enterprise security teams (PCI-DSS / ISO 27001 / APRA CPS 234) can verify what shipped and prove it came from this repo's workflow. What's attached to each release now: - coda-sbom.cdx.json — CycloneDX SBOM (Python + npm deps via syft) - coda-sbom.cdx.json.cosign.bundle — cosign keyless signature bundle (cert + signature + Rekor inclusion proof) Signing uses GitHub OIDC — no long-lived keys. The signing identity is anchored to this workflow path and the release tag, and a public transparency-log entry is recorded in Rekor. Workflow changes: - Added `id-token: write` permission (required for OIDC keyless signing) - Added anchore/sbom-action step (SHA-pinned, format=cyclonedx-json) - Added sigstore/cosign-installer + sign-blob + in-workflow verify - Extended softprops/action-gh-release `files:` to attach both artefacts README changes: - New "Verifying release provenance" subsection with the cosign verify-blob command operators can run. Co-authored-by: Isaac --- .github/workflows/release.yml | 45 +++++++++++++++++++++++++++++++++++ README.md | 22 +++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e9973c9..7ba7d2f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,9 @@ jobs: runs-on: databrickslabs-protected-runner-group permissions: contents: write + # id-token: write is required for cosign keyless signing via GitHub OIDC. + # Without it, cosign falls back to interactive auth and the workflow hangs. + id-token: write steps: - name: Checkout @@ -111,6 +114,45 @@ jobs: git tag -a "$TAG" -m "Release $TAG" git push origin "$TAG" + # ----- Supply-chain provenance: SBOM + cosign keyless signature --------- + # Generates a CycloneDX SBOM from the repo (Python + npm package metadata), + # then signs it with cosign using a short-lived OIDC token from GitHub. + # Verifiers can confirm the SBOM came from this workflow at this tag via: + # cosign verify-blob --bundle coda-sbom.cdx.json.cosign.bundle \ + # --certificate-identity-regexp 'https://github.com/databrickslabs/coding-agents-databricks-apps/.+' \ + # --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + # coda-sbom.cdx.json + - name: Generate CycloneDX SBOM + uses: anchore/sbom-action@9f7302141466aa6482940f15371237e9d9f4c34a # v0.20.5 + with: + path: . + format: cyclonedx-json + output-file: coda-sbom.cdx.json + # Don't auto-upload; we attach via softprops below for one consistent release. + upload-artifact: false + upload-release-assets: false + + - name: Install cosign + uses: sigstore/cosign-installer@d7d6e07ee54d2049ce5cdfc7eed4d6a6ccd80f5b # v3.5.0 + with: + cosign-release: v2.4.1 + + - name: Sign SBOM with cosign (keyless OIDC) + run: | + # --yes auto-confirms the Sigstore transparency log entry (Rekor). + # The resulting bundle contains the signature + certificate + Rekor + # inclusion proof in one self-contained file — easier for downstream + # verifiers than separate .sig/.cert files. + cosign sign-blob --yes \ + --bundle coda-sbom.cdx.json.cosign.bundle \ + coda-sbom.cdx.json + # Sanity check: verify what we just signed before publishing. + cosign verify-blob \ + --bundle coda-sbom.cdx.json.cosign.bundle \ + --certificate-identity-regexp 'https://github.com/${{ github.repository }}/.+' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + coda-sbom.cdx.json + - name: Create GitHub Release uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3 with: @@ -118,3 +160,6 @@ jobs: name: "${{ steps.version.outputs.TAG }}" body: ${{ steps.notes.outputs.NOTES }} prerelease: ${{ inputs.prerelease }} + files: | + coda-sbom.cdx.json + coda-sbom.cdx.json.cosign.bundle diff --git a/README.md b/README.md index f179bed..2227586 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,28 @@ This template repo opens that vision up for every Databricks user — no IDE set Single-user app — the owner is resolved via the app's service principal and Apps API (`app.creator`), with no PAT required at deploy time. Authorization checks `X-Forwarded-Email` against `app.creator`. On first terminal session, the user pastes a short-lived PAT interactively. Tokens auto-rotate every 10 minutes (15-minute lifetime), with old tokens proactively revoked. On restart, the user re-pastes (no persistence by design). +### Verifying release provenance + +Each GitHub Release ships with: + +- `coda-sbom.cdx.json` — CycloneDX SBOM of every Python + npm dependency (generated by [syft](https://github.com/anchore/syft)). +- `coda-sbom.cdx.json.cosign.bundle` — [Sigstore](https://www.sigstore.dev/) keyless signature bundle (cert + signature + Rekor inclusion proof in one file). + +To verify a release came from this repo's release workflow: + +```bash +TAG=v1.0.0 # the release you downloaded +gh release download "$TAG" -p 'coda-sbom.cdx.json*' + +cosign verify-blob \ + --bundle coda-sbom.cdx.json.cosign.bundle \ + --certificate-identity-regexp 'https://github.com/databrickslabs/coding-agents-databricks-apps/.+' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + coda-sbom.cdx.json +``` + +Signing uses GitHub's OIDC token — no long-lived signing keys exist. The signing identity is anchored to the workflow path + tag ref, and a public transparency-log entry is recorded in Rekor. + ### Gunicorn Production uses `workers=1` (PTY state is process-local), `threads=16` (concurrent polling + WebSocket), `gthread` worker class, `timeout=60` (long-lived WebSocket connections).