Dependency Verification, Checksums, Signatures, Repository Content Filtering, and Supply-Chain Security: Concepts, Architecture, and Mental Model
A reproducible dependency graph is not automatically trustworthy. Learn to separate version intent, repository origin, byte integrity, signing provenance, wrapper identity, plugin trust, and cache state before deciding whether a build input is acceptable.
Learning objectives
- Separate immutable coordinates, repository origin, integrity, authenticity, and vulnerability status.
- Explain Gradle dependency verification, trusted keys, repository content filtering, and Wrapper verification.
- Explain Maven checksum policy, Wrapper checksum controls, and repository-manager/signature boundaries without pretending Maven has Gradle verification metadata.
- Identify plugins, annotation processors, build extensions, init scripts, and wrappers as executable supply-chain inputs.
- Use inspection evidence before changing repositories, verification metadata, or caches.
Version baseline. Gradle 9.7.1 is the Gradle baseline; Maven 3.9.16 is the Maven GA baseline; Maven Wrapper 3.3.4 is the stable Wrapper baseline. JDK 21 runs the lab tools while generated Java bytecode targets release 17.
1. The build can be reproducible and still consume the wrong bytes
Earlier chapters made coordinates explicit, locked versions, modeled repositories, and built reproducible artifacts. Those controls answer questions such as “which version did we request?” and “can the same declared graph resolve again?” They do not answer “did the repository return the same reviewed bytes?” or “was this artifact actually published by the expected maintainer?”
Supply-chain hardening adds independent checks. A lockfile can pin
1.0.0 perfectly while a compromised repository serves
different bytes under 1.0.0. A SHA-256 digest can prove
bytes are unchanged yet say nothing about who originally produced
them. A valid PGP signature can prove possession of a private key,
but only an independently verified key fingerprint connects that key
to an expected publisher.
2. Mental model: identity → eligible repository → bytes → provenance → execution
flowchart TD A[Declared coordinate + version] --> B[Repository eligibility] B --> C[Resolved metadata] C --> D[Artifact bytes] D --> E[Checksum verification] D --> F[Signature verification] E --> G[Accepted build input] F --> G H[Wrapper JAR + distribution] --> I[Build tool executes] J[Plugins / processors / extensions] --> I G --> I K[Independent policy / review] --> B K --> E K --> F
The first arrow selects identity, the second limits where that identity may come from, the next retrieves metadata/artifacts, and verification checks the returned bytes/provenance before execution. The Wrapper sits even earlier: its JAR/scripts and distribution can execute the build, so validating dependency metadata but blindly trusting a changed Wrapper leaves a bootstrap gap.
3. Define every control before mutating it
| Control / state | What it proves | What it does not prove | Primary file / evidence |
|---|---|---|---|
| Immutable coordinate | The requested name/version is explicit. | That the bytes at those coordinates are trusted. | Build file, lockfile, dependency report. |
| Checksum | Exact bytes match an independently accepted digest. | Who produced the bytes, unless the digest itself is authenticated. |
verification-metadata.xml,
.sha256/.sha512.
|
| PGP signature | Artifact was signed by the holder of a key. | That the key belongs to the expected publisher unless fingerprint trust is independently anchored. |
.asc, key fingerprint, trusted-key policy.
|
| Repository filter | A repository is eligible only for a declared namespace/content set. | Artifact integrity once downloaded. |
Gradle repository content {} or
exclusiveContent.
|
| Dependency verification | Gradle checks external dependency/plugin artifacts against committed verification policy. | Vulnerability-free code or trustworthy initial bootstrap. |
gradle/verification-metadata.xml + failure
report.
|
| Wrapper verification | The build-tool bootstrap/distribution matches expected bytes. | That project build logic is safe to execute. | Wrapper properties + published Wrapper/distribution digests. |
| Maven checksum policy | Resolver reacts to repository checksum absence/mismatch according to configured policy. | A committed per-artifact provenance ledger equivalent to Gradle verification metadata. | Repository policy + resolver diagnostics. |
| Repository manager policy | Central routing, allow/deny, quarantine, credential and provenance rules can be enforced outside project files. | Project-level intent unless configuration is versioned/audited. | Manager config, mirror settings, audit logs. |
4. Gradle dependency verification is a committed acceptance policy
Gradle stores dependency verification policy in
gradle/verification-metadata.xml. Once present,
verification is strict by default. It covers
external artifacts and metadata resolved by Gradle’s dependency
engine, including project/settings plugins. The file is global to
the current build: root project, subprojects, and
buildSrc share it; included-build behavior must be
reviewed when composition changes.
Generation is a bootstrap mechanism, not independent trust.
--write-verification-metadata sha256 records what the
current repositories serve. If those bytes are already malicious,
the generated checksum faithfully records the malicious bytes.
Review therefore needs an independent source: publisher release
checksums, signed release pages, organization-approved
repository-manager evidence, source-built comparison, or another
trustworthy channel.
# Read-only state first.
./gradlew --version
./gradlew dependencies --configuration compileClasspath
# Bootstrap only after repository + coordinate review.
./gradlew --write-verification-metadata sha256 compileJava
# Review before accepting.
git diff -- gradle/verification-metadata.xml
5. Checksums and signatures are complementary, not substitutes
A modern cryptographic checksum gives a byte fingerprint. A signature binds artifact content to a key. For strongest assurance, Gradle documentation recommends checksums plus signatures where signatures are available. The policy must also scope trusted keys narrowly: a key trusted for one group/module should not automatically become a universal publisher identity.
| Question | Checksum | Signature |
|---|---|---|
| Did the bytes change? | Strong answer with SHA-256/SHA-512. | Yes if signature uses acceptable cryptography, but still pair with a strong checksum. |
| Who produced them? | No answer by itself. | Only after independently verifying the public-key fingerprint/ownership. |
| Can repository compromise be detected? | Yes if expected digest came from an independent trusted source. | Yes if attacker cannot sign with a trusted key and key policy is scoped. |
| Upgrade workflow | New version normally needs reviewed new digests. | May use same trusted key, but key rotation must be investigated and documented. |
6. Repository order is a security input; exclusivity is stronger than inclusion
A normal Gradle content { includeGroup(...) } filter
says what one repository may contain; it does not automatically
prevent other repositories from serving the same group.
exclusiveContent is stronger: matching coordinates can
only come from the selected repository. This is exactly the control
needed for an internal namespace that must never fall through to an
alternate public repository.
pluginManagement {
repositories {
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
// Public repository is deliberately listed first.
maven {
name = "alternateRepo"
url = uri("alternate-repo")
}
// The internal namespace is exclusive to this repository.
exclusiveContent {
forRepository {
maven {
name = "academyInternal"
url = uri("trusted-repo")
metadataSources { mavenPom(); artifact() }
}
}
filter {
includeGroup("dev.academy.internal")
}
}
}
}
rootProject.name = "chapter28-supply-chain"
The intentionally suspicious detail is that
alternateRepo is listed first. For
dev.academy.internal, that order no longer matters:
exclusivity removes the alternate repository from eligibility for
the protected group.
7. Plugin repositories are a separate executable-code boundary
The pluginManagement.repositories block governs plugin
resolution and is distinct from normal dependency repositories.
Plugins execute build logic and may read environment variables,
configure repositories, launch tools, or publish artifacts. Pin
plugin versions, scope plugin repositories deliberately, and
remember that Gradle dependency verification also covers artifacts
resolved for plugins. A broad Plugin Portal declaration is not
automatically wrong; it is simply a trust decision that must be
explicit.
8. Verify the tool before asking the tool to verify dependencies
Gradle Wrapper integrity has two separate checks.
distributionSha256Sum verifies the downloaded Gradle
ZIP. The committed gradle-wrapper.jar must also be
checked against Gradle’s published Wrapper-JAR checksum or PGP
signature, especially on Wrapper upgrades. Gradle 9.7.1 documents
both workflows.
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Do not “repair” a Wrapper mismatch by calculating a checksum from the file already in the pull request and accepting that value. The expected digest must come from Gradle’s independent published release data.
9. Maven: transport checksums, Wrapper checksums, signatures, and manager policy
Maven Resolver has repository checksum policy. In Maven 3.9.16
model/repository policy, fail rejects
missing/mismatched repository checksums, warn reports
them, and lower-level resolver APIs also expose ignore behavior.
Maven Wrapper 3.3.4 can pin SHA-256 values for the Maven
distribution and, when a wrapper JAR is used, the wrapper JAR
itself.
Core Maven does not provide a project-level committed
per-artifact verification ledger equivalent to Gradle’s
verification-metadata.xml. Stronger coordinate routing,
signature enforcement, quarantine, and namespace policy are commonly
enforced by a repository manager or specialized tooling/extensions.
Teach that boundary rather than pretending
dependencyManagement, a lock surrogate, or
checksumPolicy proves provenance.
# .mvn/wrapper/maven-wrapper.properties
# Pin these from independently verified Apache Maven Wrapper/Maven release checksums.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
distributionSha256Sum=<verified-sha256>
# wrapperSha256Sum applies when your wrapper distribution includes maven-wrapper.jar.
wrapperSha256Sum=<verified-wrapper-jar-sha256>
10. Read-only inspection before policy changes
Preserve evidence before editing repositories or verification files.
./gradlew --version
cat gradle/wrapper/gradle-wrapper.properties
sha256sum gradle/wrapper/gradle-wrapper.jar
sed -n '1,220p' settings.gradle.kts
./gradlew dependencies --configuration compileClasspath
# Maven lane if the project already has its trusted wrapper:
./mvnw --version
./mvnw help:effective-settings -DshowPasswords=false
./mvnw dependency:tree
If a command would execute unreviewed build logic, stop. Do not run a Wrapper merely because it exists in an untrusted checkout; validate the bootstrap first.
11. DevOps operating model
Supply-chain controls become operational evidence: exact Wrapper identity, repository list/scope, dependency/plugin coordinates, verification policy diff, key fingerprints, and failure reports belong in review and CI artifacts. A dependency incident is therefore not “delete the cache and rerun.” It is an evidence-preservation and trust-reconstruction problem.
Knowledge check
Why does a lockfile not prove dependency integrity?
It records/resolves version state, but the repository could serve different bytes under the same locked coordinate.
Why is a SHA-256 checksum not proof of publisher identity?
A digest identifies bytes, not who created them; its expected value also needs a trusted distribution channel.
What is stronger for an internal namespace: includeGroup or exclusiveContent?
exclusiveContent, because matching coordinates become ineligible from other repositories.
What is wrong with regenerating verification metadata immediately after a mismatch?
It can auto-accept compromised bytes and destroy the original signal instead of investigating it.
Does Maven checksumPolicy create Gradle-style verification metadata?
No. It controls repository checksum handling; per-artifact provenance/allow-list policy is a separate concern.
Official references and version notes
- Gradle 9.7.1 release notes — pinned Gradle baseline and current dependency-verification diagnostics.
- Verifying Dependencies — checksums, signatures, trusted keys, strict/lenient/off modes, bootstrapping, reports, and scope.
-
Filtering Repository Content
— repository filters and
exclusiveContentsemantics. -
Gradle Wrapper
—
distributionSha256Sum, Wrapper JAR verification, PGP verification, and upgrade workflow. - Securing Gradle Builds — dependency, repository, cache, CI, and Wrapper attack surfaces.
- Maven release history — Maven 3.9.16 GA baseline; Maven 4 remains pre-GA at this chapter timestamp.
- Maven settings reference — repositories, mirrors, servers, and repository policies.
- Maven Resolver RepositoryPolicy — checksum fail/warn/ignore behavior.
- Apache Maven Wrapper — Wrapper 3.3.4 and SHA-256 verification properties.
- Apache Maven downloads — release checksums/signatures and KEYS verification guidance.
Version-sensitive behavior was rechecked against primary documentation on 2026-08-24. Mandatory labs are local/free and use synthetic artifacts only. No production repository, credential, signing key, shared cache, or hosted CI service is required.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.