Repository navigation
[SECURITY NOTICE] partial credential exposure in public CI logs (no user action required) #3089
Description
No released artifact is affected. Nothing was published by anyone but us, and we have verified that. No action is required of Spotless users.
What was exposed
While debugging a publishing change, two secrets were printed into public GitHub Actions logs:
- Our Sonatype/Maven Central token password — but not the token username.
- Our GPG signing subkey, still encrypted — but not the passphrase that decrypts it (>100 bits of entropy, never printed anywhere).
- Our master GPG key was not exposed. The CI copy contains only the signing subkey.
Each leaked value was one half of a pair whose other half stayed masked. The logs were public for roughly 28 hours, so we assume they were copied; nothing below depends on them having gone unread.
How it happened
Both secrets were stored base64-encoded, a leftover from an older CI system. Base64 is an encoding, not protection.
Our previous build decoded them inside Gradle, so the plaintext never left the build process. When we modernized publishing, the new plugin reads plaintext Gradle properties instead, so a workflow step decoded the secrets and wrote them to $GITHUB_ENV.
GitHub masks a secret only when the logged text matches the value it was given. A decoded value is a different string, so it is not masked — and variables written to $GITHUB_ENV are printed in the log header of every later step. Both decoded values were printed there.
Sequence:
- #3052 migrated publishing to a new plugin. The publish job only ran on
main, so the new path was never exercised by a PR. - #3079 added snapshot publishing from
main, which put release credentials into every push tomain. - After merging, publishing failed on
main. While diagnosing it in #3086, the branch restriction on the publish job was temporarily removed so the job would run on the PR, and the decoded secrets were printed in those runs. Secrets were available to any branch in the repository; only that restriction had been keeping them out of PRs. - Because publishing had changed, we ran a security review of everything merged since the previous release, which caught it.
What we verified
- All 68
com.diffplugrelease coordinates on Maven Central are untouched since the 8.10.2 release on 2026-09-04. - The snapshot repository contains only the two expected test uploads from our own CI.
- Every published file is signed by our legitimate key, with signature timestamps matching our CI jobs.
- The Central Portal shows no other deployments.
Response
- ~25 minutes from detection to contained: pending CI cancelled, the exposed logs deleted, and the Sonatype token revoked and rotated.
- The base64 secrets were deleted outright, organization-wide, so no workflow can decode a secret again.
mainis locked, and publishing is currently paused while we finish the key work.
Signing key
The leaked subkey was not compromised — the passphrase protecting it has never leaked — but we are retiring it anyway. We are issuing a new signing subkey and deleting every copy of the passphrase that protects the old one, which leaves the exposed key material permanently unusable.
We are not revoking the old subkey, so signatures on existing releases remain valid.
If you use Gradle dependency verification or otherwise pin our signing key, you will need to add the new subkey before verifying a future release. We will post the new subkey ID as a follow-up comment on this issue within hours.
- New signing subkey:
0xFA5C18CFB74EC7C6, under the same primary key
27294482F2D2F9A318CA041B1AAAA7AB37293D3E. It is published on keys.openpgp.org and
keyserver.ubuntu.com, including by-email lookup forsecurity@diffplug.com.
Policy changes
It should never have been possible for these credentials to be reachable from a PR, and exposing them to every push to main for snapshot publishing was an unacceptable risk.
- Snapshot publishing from
mainhas been removed. We no longer publish snapshots. - Release credentials now live in a deployment environment restricted to the
releasebranch, which only one maintainer can push to. No PR, topic branch, fork, or push tomaincan reach them. - Secrets are stored in the exact form the build consumes. Nothing decodes a secret in CI.
- CI tokens are least-privilege by default, and changes to workflow and build files now require review from a code owner.
Security reports: spotless-security@diffplug.com
I will follow-up in this issue when publishing has been restored and we are accepting pull requests again. I expect we will not publish a new release or accept PRs until next week.
Activity
nedtwigg commentedon Sep 17, 2026
mainis open, we're accepting PRs again0xFA5C18CFB74EC7C6, under the same primary key27294482F2D2F9A318CA041B1AAAA7AB37293D3E. It is published on keys.openpgp.org and keyserver.ubuntu.com, including by-email lookup forsecurity@diffplug.com.The old subkey
0x4272C851is not revoked, so signatures on existing releases remain valid. It will simply never sign again, and the passphrase protecting the exposed copy has been destroyed.Goooler commentedon Sep 18, 2026
I'm so sorry about this. I was trying to bridge the base64 secrets to decoded values, but overlooked that security should always be the top priority. I'm really sorry for causing so much trouble and inconvenience for you and the team. Really appreciate your quick response and the swift resolution!
nedtwigg commentedon Sep 18, 2026
Not your fault! The system was too fragile, good that it was broken during friendly work rather than unfriendly.
nedtwigg commentedon Sep 25, 2026
The September 25 publishing runs have been reviewed, and no evidence of credential exposure was found in their GitHub Actions logs.
The review covered the complete logs from all five deploy runs, including setup, commands, environment headers, exceptions, and cleanup:
In the credential-bearing runs, the Maven Central username/password, armored signing key, signing passphrase, Gradle Plugin Portal credentials, GitHub token, and logged Git authorization header are masked as
***. No private-key armor blocks, unmasked credential assignments, or suspicious encoded credential payloads were found. The visible signing subkey ID is public information. The deployed workflow passes the Maven Central and GPG secrets unchanged from themaven-centralenvironment; the previous Base64 decoding/export path is absent.Closing this credential-exposure incident. This finding is scoped to the reviewed GitHub Actions logs; linked Develocity scans and cached artifacts were not audited.
Separate release follow-up: at the time of review, the
spotless-libandspotless-lib-extra4.10.3 POMs returned HTTP 404 from Maven Central, while the available Maven plugin 3.10.3 POM depends on them. Closing this security issue does not confirm that every release artifact is available.