Chapter 28Lesson 01~270 minutes

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.

Gradle 9.7.1Maven 3.9.16IntegrityAuthenticityTrust boundaries

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

Supply-chain flow
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?

Why is a SHA-256 checksum not proof of publisher identity?

What is stronger for an internal namespace: includeGroup or exclusiveContent?

What is wrong with regenerating verification metadata immediately after a mismatch?

Does Maven checksumPolicy create Gradle-style verification metadata?

Official references and version notes

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.