Single-plugin entry point for Maven supply-chain hardening.
| Check | Default | Description |
|---|---|---|
minReleaseAge |
P3D |
Rejects artifacts published less than N days ago. |
requireExactVersions |
on | Rejects version ranges, LATEST, RELEASE. |
repositoryAllowlist |
Central only | Effective repositories must be on the allowlist. |
baseline |
auto | Enforces .supply-chain-baseline.json if present. |
checksumStrict |
warn | Warns when Maven is not in strict checksum mode. |
requireReleaseDeps |
on | Rejects SNAPSHOT dependencies. |
bannedDependencies |
empty list | User-supplied ban list. |
dependencyConvergence |
on | Flags multiple resolved versions of the same GA. |
pgpSignature |
on | Verifies .asc signatures against keyserver / pinned map. |
Add to your pom.xml:
<plugin>
<groupId>com.wlami</groupId>
<artifactId>supply-chain-maven-plugin</artifactId>
<version>0.2.0</version>
<extensions>true</extensions>
</plugin>That's it. <extensions>true</extensions> activates a lifecycle participant that auto-binds the check goal to the validate phase. No <executions> block needed. Empty configuration applies the full hardened defaults.
If you'd rather declare the execution explicitly (or want to bind to a different phase), drop <extensions>true</extensions> and add <executions><execution><goals><goal>check</goal></goals></execution></executions> the usual way.
PGP is on by default. Most artifacts on Maven Central ship a .asc, but plenty don't (older releases, internal mirrors, deps from non-Central repos). Two ways to deal:
Disable globally - if signatures aren't available across your dep graph:
<configuration>
<checks>
<pgpSignature>false</pgpSignature>
</checks>
</configuration>Disable per-artifact (preferred when only a handful lack signatures) - keep PGP on, let the offending artifacts pass:
<configuration>
<pgpKeysMap>${project.basedir}/pgp-keys-map.list</pgpKeysMap>
</configuration>pgp-keys-map.list:
# Allow these specific GAVs to pass without a signature.
com.legacy:unsigned-artifact:* = noSig
io.github.example:other-unsigned = noSig
(The noSig token is documented separately; for now the cleanest "I know what I'm doing" path is the global toggle above.)
If you also see timeouts contacting keys.openpgp.org, the verifier emits a per-artifact finding under whatever onNetworkError is set to (default WARN).
Publishing your own artifact and want to consume it immediately without waiting three days? Add it to the dedicated exclusion list:
<configuration>
<minReleaseAgeExclusions>
<exclusion>com.wlami:my-artifact</exclusion>
</minReleaseAgeExclusions>
</configuration>Pin to groupId:artifactId. Avoid groupId:* - it matches future artifacts you have not published yet. Combine with <repositoryAllowlist> so the exempted artifact still has to come from a trusted repo.
supply-chain:check- run all enabled checks (bound tovalidate).supply-chain:report- run checks without failing the build.supply-chain:dump-baseline- write a baseline file of currently resolved deps.supply-chain:refresh-cache- clear the release-date cache.
target/supply-chain.sarif is always produced. Upload to GitHub Code Scanning:
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: target/supply-chain.sarifSee docs/superpowers/specs/2026-05-14-supply-chain-maven-plugin-design.md.
Releases run on GitHub Actions via .github/workflows/release.yml. The workflow signs with PGP and publishes through the Sonatype Central Portal.
| Fingerprint | 84B3FFEC2085A4CBC92D0DF759E617E4BBD5963D |
mitzel@tawadi.de |
|
| Stored as | GitHub Actions secrets MAVEN_GPG_PRIVATE_KEY + MAVEN_GPG_PASSPHRASE |
| Local backup | ~/Library/Application Support/wlami/supply-chain-mvn-plugin-signing.txt (mode 600) |
| Uploaded to | keys.openpgp.org (verify email link to publish UID) |
| Secret | Purpose |
|---|---|
MAVEN_CENTRAL_USERNAME |
Sonatype Central Portal user token (username) |
MAVEN_CENTRAL_PASSWORD |
Sonatype Central Portal user token (password) |
MAVEN_GPG_PRIVATE_KEY |
Armored private key for signing |
MAVEN_GPG_PASSPHRASE |
Passphrase for the key |
Two ways:
A. Tag-driven (recommended once autoPublish=true):
mvn versions:set -DnewVersion=0.1.0 -DgenerateBackupPoms=false
git commit -am "release: 0.1.0"
git tag v0.1.0
git push --follow-tags origin mainB. Manual dispatch: trigger Release workflow in the Actions tab; pass version=0.1.0.
After the workflow finishes, the bundle is staged on Central Portal awaiting manual promotion (because autoPublish=false in pom.xml). Promote it in the Central Portal UI. Flip autoPublish to true once you trust the pipeline.
Then bump to the next snapshot:
mvn versions:set -DnewVersion=0.2.0-SNAPSHOT -DgenerateBackupPoms=false
git commit -am "chore: bump to 0.2.0-SNAPSHOT"
git pushMAVEN_GPG_PASSPHRASE=$(cat ~/Library/Application\ Support/wlami/supply-chain-mvn-plugin-signing.txt | awk '/^Passphrase: / {print $2}') \
mvn -Prelease clean deployRequires ~/.m2/settings.xml with the Central Portal token under <server id="central">.
All changes land via pull request - main is the release-tracking branch and accepts merges from feature branches only. CI runs on every PR.
Apache-2.0.