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 to main.
- 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.diffplug release 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.
main is 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 for security@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
main has been removed. We no longer publish snapshots.
- Release credentials now live in a deployment environment restricted to the
release branch, which only one maintainer can push to. No PR, topic branch, fork, or push to main can 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.
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:
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_ENVare printed in the log header of every later step. Both decoded values were printed there.Sequence:
main, so the new path was never exercised by a PR.main, which put release credentials into every push tomain.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.What we verified
com.diffplugrelease coordinates on Maven Central are untouched since the 8.10.2 release on 2026-09-04.Response
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.
0xFA5C18CFB74EC7C6, under the same primary key27294482F2D2F9A318CA041B1AAAA7AB37293D3E. It is published on keys.openpgp.org andkeyserver.ubuntu.com, including by-email lookup for
security@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
mainfor snapshot publishing was an unacceptable risk.mainhas been removed. We no longer publish snapshots.releasebranch, which only one maintainer can push to. No PR, topic branch, fork, or push tomaincan reach them.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.