Skip to content

ci: isolate Maven Central publish secrets from Gradle plugin graph#151

Merged
gavinsharp merged 4 commits into
mainfrom
gavinsharp/workflow-hardening
Jun 29, 2026
Merged

ci: isolate Maven Central publish secrets from Gradle plugin graph#151
gavinsharp merged 4 commits into
mainfrom
gavinsharp/workflow-hardening

Conversation

@gavinsharp

@gavinsharp gavinsharp commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Splits the single Gradle-driven publish job into a credential-free build-artifact job (runs Gradle and its plugin graph, no secrets) and a credential-holding publish job that runs only curl/jq/gpg from the runner image, behind a maven-production environment. The publish job POSTs a Maven-layout bundle directly to the Sonatype Central Portal (/api/v1/publisher/upload) and polls /status until PUBLISHED/FAILED, replacing the cl.franciscosolis.sonatype-central-upload Gradle plugin that previously had all five MAVEN_* secrets exposed to the third-party plugin graph. The two jobs hand off a maven-bundle artifact guarded by a SHA256SUMS integrity check.

Hardening along the way: the parsed version is validated against ^[A-Za-z0-9._+-]+$ before it flows into shell/URL/filesystem contexts; publish secrets and shared coordinates moved to job/workflow-level env to dedupe across steps; cache: gradle added to every setup-java; actions/checkout bumped to v6.0.3 (other action pins already latest); and the now-unused .publish/prepare.sh Gradle-signing helper deleted.

Follow-ups required before this can run

  • Create the maven-production GitHub Environment, restrict it to refs/heads/main, and attach MAVEN_USERNAME / MAVEN_PASSWORD / MAVEN_SIGNATURE_SECRET_KEY / MAVEN_SIGNATURE_PASSWORD at the environment level; delete the repo-level copies. MAVEN_SIGNATURE_KID is no longer read by the workflow.
  • Rotate the Sonatype user token and the GPG signing key (subkey-only export, 12-month expiry) — the threat model that motivated this change assumes the prior long-lived secrets are revoked.

Test plan

  • Create maven-production environment + attach secrets
  • Cut a *-rc1 version on a branch, merge, observe the full compile → test → tag → build-artifact → publish pipeline
  • Verify the published jar on Maven Central, including the GPG signature against the new subkey
  • Revoke the prior Sonatype token and prior signing subkey

🤖 Generated with Claude Code


Note

Medium Risk
Changes the production Maven Central release path and GPG signing flow; misconfiguration of the new maven-production environment or bundle layout could block or break releases, though secrets are better isolated than before.

Overview
Splits release publishing into a secret-free Gradle build and a minimal publish job that talks to Sonatype Central Portal directly, instead of running ./gradlew sonatypeCentralUpload with all Maven/GPG secrets inside the third-party plugin graph.

The new build-artifact job assembles jars, POM, and optional module metadata (skipping Gradle publication signing), stages them under dist/ with a SHA256SUMS manifest, and uploads a short-lived maven-bundle artifact. publish runs in the maven-production environment with only download-artifact, checksum verification, ephemeral GPG signing (.asc plus md5/sha1 sidecars), Maven-layout zip assembly, Portal /upload, and polling /status until PUBLISHED or FAILED. The tag job now exports version and rejects unsafe version strings before they hit shell paths or upload URLs.

Hardening and housekeeping: workflow-level ARTIFACT_ID, Gradle cache on all Java setups, pinned checkout v6.0.3 and shared verify-openapi-spec / sync-fern at 1.0.3, and removal of .publish/prepare.sh (no longer used for Gradle signing prep).

Reviewed by Cursor Bugbot for commit 4082a47. Bugbot is set up for automated code reviews on this repo. Configure here.

@gavinsharp gavinsharp force-pushed the gavinsharp/workflow-hardening branch from 9be743d to edce589 Compare May 28, 2026 16:19
@gavinsharp gavinsharp marked this pull request as ready for review May 28, 2026 16:22

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3523f52. Configure here.

Comment thread .github/workflows/ci.yml
@gavinsharp gavinsharp force-pushed the gavinsharp/workflow-hardening branch from 3523f52 to 8a3eac7 Compare June 2, 2026 21:35
gavinsharp and others added 4 commits June 25, 2026 09:50
Split the publish job in two: a `build-artifact` job that runs Gradle (and
its plugin graph) but holds no credentials, and a credential-holding
`publish` job that only runs curl/jq/gpg from the runner image. The
publish job runs in a `maven-production` environment so secrets can move
off the repo and behind a branch/reviewer gate. Also pin the `version`
to a safe charset before propagating, switch to job/workflow-level env
to deduplicate, add Gradle caching to setup-java, and remove the now-
unused `.publish/prepare.sh` helper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A plain shell assignment isn't inherited by the gpg subprocess, so the
key imported into the default ~/.gnupg while GITHUB_ENV pointed the
later signing step at the empty ephemeral directory — every signature
failed. Export it in-step too.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Picks up the bundle-openapi-spec fix to fetch specs from the public
bucket. No interface change to verify-openapi-spec or the
sync-fern-artifacts caller.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@rossmpowell rossmpowell left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good -- I'm sure it's already on the radar given this is essentially what this PR is doing! But Claude had flagged that we can remove the MAVEN_SIGNATURE_KID secret with this merged

@gavinsharp gavinsharp merged commit 23d9f96 into main Jun 29, 2026
7 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.

2 participants