Skip to content

ci: gate releases on a vulnerability scan before anything goes public - #280

Merged
TheMeinerLP merged 1 commit into
mainfrom
ci/use-shared-supply-chain
Aug 8, 2026
Merged

ci: gate releases on a vulnerability scan before anything goes public#280
TheMeinerLP merged 1 commit into
mainfrom
ci/use-shared-supply-chain

Conversation

@TheMeinerLP

Copy link
Copy Markdown
Contributor

Depends on OneLiteFeatherNET/workflows#23do not merge before that is released as v2.6.0, the two uses: pins point at that tag.

The pipeline now breaks before anything is public

release-please → build → security-gate → publish → sbom

build produces the jar and the SBOM and publishes nothing. security-gate scans the jar that is about to ship and fails on CRITICAL or HIGH. publish runs only when the gate is green, so a vulnerable build can no longer reach the GitHub release, Hangar or Modrinth.

publish deliberately does not use always() — that would have run it regardless of a failed gate. It uses !cancelled() && needs.security-gate.result == 'success' && (…) instead.

The gate scans the artifact, not the source tree

This is the part that would have been easy to get wrong. Measured on this repository:

Scan Packages detected Findings
trivy fs on the source tree 0 real (only stray jacoco pom.xml from build caches) 0
trivy fs on the shaded jar 0 0
trivy rootfs on the shaded jar 12 1 HIGH

A gate on either fs variant would have reported green while checking nothing at all. Gradle without a gradle.lockfile gives fs nothing to read, and Trivy's fs scanner does not look inside JARs.

That one HIGH is real, and is fixed here

CVE-2023-5072org.json below 20231013 can be driven into a parser denial of service. It reaches our shaded jar transitively:

org.json:json:20230618
\--- club.minnced:discord-webhooks:0.8.4

Fixed with a dependency constraint forcing 20231013. Verified end to end:

before:  12 packages, 1 HIGH (CVE-2023-5072, org.json 20230618)
after :  org.json:json:20230618 -> 20231013
         12 packages, 0 findings

Without this the new gate would block every single release on day one.

SBOM upload moves out of the release job

The Dependency-Track upload now runs as its own job via the shared sbom-publish.yml, fed the CycloneDX artifact the build produced. An unreachable server or a rejected API key can no longer take the release with it — which is exactly what happened to 2.9.0, where one rejected platform version cost the JAR, both platform releases and the SBOM at once.

Note this does not fix the 403 that 2.9.0's SBOM upload hit. That is a Dependency-Track API key permission: with autocreate on, the key's team needs PROJECT_CREATION_UPLOAD in addition to BOM_UPLOAD. Still needs doing on the server side.

security.yml — continuous monitoring on top

The gate only sees a release at the moment it is cut. Most CVEs are published against code that has not changed since, so security.yml scans main plus a weekly sweep, report-only, with findings in code scanning.

No pull_request trigger on purpose: fork PRs get a read-only token, so the SARIF upload would fail for exactly the contributions that most need review.

Cost

publish re-runs Gradle for the Hangar and Modrinth tasks, since those are Gradle tasks and cannot consume a downloaded jar. With the setup-gradle cache that is a cache-hit rebuild, and it buys the guarantee that nothing is published before the gate.

Adopts the shared supply-chain workflows and, more importantly, puts a
scan in front of every public step.

Release pipeline is now:

  release-please -> build -> security-gate -> publish -> sbom

build produces the jar and the SBOM and publishes nothing. security-gate
scans the jar that is about to ship and fails on CRITICAL or HIGH.
publish only runs when the gate is green, so a vulnerable build can no
longer reach the GitHub release, Hangar or Modrinth. publish deliberately
does not use always(), which would have run it despite a failed gate.

The gate scans the built artifact with rootfs, not the source tree with
fs. Measured on this repository:

  trivy fs     source tree   ->  0 packages,  0 findings
  trivy fs     shaded jar    ->  0 packages,  0 findings
  trivy rootfs shaded jar    -> 12 packages,  1 HIGH

A gate on either fs variant would have been green while checking nothing.

That one HIGH is real and is fixed here: CVE-2023-5072 in org.json below
20231013, pulled in transitively by club.minnced:discord-webhooks, which
still resolves 20230618. A dependency constraint forces 20231013; after
it the same scan reports 12 packages and no findings. Without the fix
the new gate would block every release on day one.

The Dependency-Track upload moves out of the publish job into the shared
sbom-publish workflow, so an unreachable server or a rejected API key
can no longer take the release with it - which is exactly what happened
to 2.9.0.

security.yml adds continuous monitoring on top: main plus a weekly sweep,
report-only, findings in code scanning. No pull_request trigger, because
fork PRs get a read-only token and the SARIF upload would fail for
exactly the contributions that most need review.
@TheMeinerLP
TheMeinerLP requested a review from a team as a code owner August 8, 2026 14:42
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Unit Test Results

  8 files    8 suites   1s ⏱️
 73 tests  73 ✅ 0 💤 0 ❌
133 runs  133 ✅ 0 💤 0 ❌

Results for commit 94c8d04.

@TheMeinerLP
TheMeinerLP merged commit a956f9c into main Aug 8, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant