Checkpoint Lab — Dependency Verification, Checksums, Signatures, Repository Content Filtering, and Supply-Chain Security
Create one synthetic Maven/Gradle supply-chain matrix, prove an integrity failure and a repository-scope failure with local artifacts, record exact wrapper/dependency/plugin identities, and finish with an incident-ready verification checklist for CI.
Learning objectives
- Build a documented Maven/Gradle control matrix for dependency, repository, plugin, and Wrapper trust.
- Record exact dependency/plugin/Wrapper identities and independent checksum evidence.
- Demonstrate one harmless integrity failure and one repository-scope failure using local artifacts.
- Prove repair in strict, isolated state without disabling verification or deleting normal caches.
- Produce an incident-ready verification checklist and bridge to CI/CD enforcement in Chapter 29.
Checkpoint boundary. Everything is synthetic and project-local. The checkpoint must never use production credentials, production publish targets, normal user caches, or a shared signing key.
1. Acceptance contract
The checkpoint passes only if you can show both what should resolve and what must fail. A green build alone is insufficient.
| Invariant | Required evidence |
|---|---|
| Build-tool identity | Gradle Wrapper version/URL/distribution SHA-256 + Wrapper JAR independent verification record; Maven Wrapper identity if used. |
| Dependency identity |
Exact coordinate
dev.academy.internal:policy-lib:1.0.0 + trusted
JAR SHA-256.
|
| Repository ownership |
Internal group is exclusive to trusted-repo;
alternate repo cannot satisfy it.
|
| Gradle verification |
Reviewed gradle/verification-metadata.xml;
strict build passes for trusted bytes.
|
| Integrity incident | Same-coordinate changed JAR fails strict verification; expected/actual digests preserved. |
| Maven lane | Checksum policy and independent checksum/signature evidence are documented without claiming a Gradle-equivalent core ledger. |
| Cleanup | Only disposable lab/cache/key state is removed. |
2. Setup and preflight
Create the lab and record the environment before any mutation.
set -euo pipefail
mkdir ch28-checkpoint
cd ch28-checkpoint
export GRADLE_USER_HOME="$PWD/.gradle-user-home"
java -version 2>&1 | tee environment-java.txt
javac -version | tee environment-javac.txt
./gradlew --version | tee environment-gradle.txt
cat gradle/wrapper/gradle-wrapper.properties | tee environment-gradle-wrapper.txt
sha256sum gradle/wrapper/gradle-wrapper.jar | tee environment-wrapper-jar.actual.sha256
# Optional Maven lane if the trusted wrapper is already available:
# ./mvnw --version | tee environment-maven.txt
3. Predict before execution
Write the predictions before running the experiment:
P1. With trusted-repo present, dev.academy.internal:policy-lib:1.0.0 resolves only there.
P2. After verification metadata is accepted, unchanged trusted bytes pass strict verification.
P3. Replacing those bytes under the same coordinate produces a non-zero verification failure.
P4. If trusted-repo cannot serve the coordinate, alternate-repo still cannot satisfy the exclusive internal group.
P5. Restoring trusted bytes and repository state returns the exact original SHA-256 and a green strict build.
4. Create the control matrix
Keep this table with the checkpoint evidence and mark each row PASS/FAIL after execution.
| Boundary | Gradle control | Maven control / boundary | Evidence |
|---|---|---|---|
| Version identity | Pinned coordinate + prior Chapter 20 governance. | Pinned dependency/plugin versions / dependencyManagement policy. | Dependency report/tree. |
| Repository origin |
Settings repositories + exclusiveContent.
|
Mirrors/repositories + repository-manager routing when strict namespace ownership is required. | Repository config + scope-failure test. |
| Artifact integrity |
verification-metadata.xml SHA-256, strict mode.
|
Repository checksum policy + independent SHA-256/SHA-512 verification. | Expected/actual digest. |
| Authenticity | Trusted PGP keys + signatures when independently anchored. | External GPG/repository-manager/tooling verification where required. | Fingerprint + signature result. |
| Build tool | Distribution SHA-256 + Wrapper JAR checksum/PGP. | Wrapper 3.3.4 distribution/JAR SHA-256 properties as applicable. | Wrapper files + independent release digest. |
| Plugins | Pinned plugin IDs/versions + plugin repositories + dependency verification. | Pinned build plugins/extensions + governed plugin repositories. | Plugin declarations/resolution evidence. |
| Incident response | Strict failure + verification report + isolated rerun. | Resolver logs/effective settings + isolated local repo + manager audit. | Preserved incident bundle. |
5. Create the synthetic repositories and consumer
Use the exact Lesson 2 fixture so the test is reproducible.
set -euo pipefail
mkdir -p trusted-repo/dev/academy/internal/policy-lib/1.0.0
mkdir -p alternate-repo/dev/academy/internal/policy-lib/1.0.0
mkdir -p fixture-src/dev/academy/internal fixture-classes
cat > fixture-src/dev/academy/internal/PolicyLib.java <<'JAVA'
package dev.academy.internal;
public final class PolicyLib {
private PolicyLib() {}
public static String message() { return "trusted-v1"; }
}
JAVA
javac --release 17 -d fixture-classes fixture-src/dev/academy/internal/PolicyLib.java
jar --create --date=2026-01-01T00:00:00Z \
--file trusted-repo/dev/academy/internal/policy-lib/1.0.0/policy-lib-1.0.0.jar \
-C fixture-classes .
cat > trusted-repo/dev/academy/internal/policy-lib/1.0.0/policy-lib-1.0.0.pom <<'POM'
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>dev.academy.internal</groupId>
<artifactId>policy-lib</artifactId>
<version>1.0.0</version>
</project>
POM
# Create an alternate repository entry with the same coordinate but different bytes.
printf 'not-the-trusted-jar\n' > alternate-repo/dev/academy/internal/policy-lib/1.0.0/policy-lib-1.0.0.jar
cp trusted-repo/dev/academy/internal/policy-lib/1.0.0/policy-lib-1.0.0.pom \
alternate-repo/dev/academy/internal/policy-lib/1.0.0/policy-lib-1.0.0.pom
sha256sum trusted-repo/dev/academy/internal/policy-lib/1.0.0/policy-lib-1.0.0.jar \
| tee trusted-artifact.sha256
sha256sum alternate-repo/dev/academy/internal/policy-lib/1.0.0/policy-lib-1.0.0.jar \
| tee alternate-artifact.sha256
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"
plugins {
java
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
tasks.withType<JavaCompile>().configureEach {
options.release.set(17)
}
dependencies {
implementation("dev.academy.internal:policy-lib:1.0.0")
}
package dev.academy.app;
import dev.academy.internal.PolicyLib;
public final class App {
private App() {}
public static String message() { return PolicyLib.message(); }
}
6. Establish and independently record the baseline
Bootstrap SHA-256 metadata, then compare the generated artifact digest to the independently calculated trusted repository digest before treating the metadata as accepted.
./gradlew --write-verification-metadata sha256 clean compileJava \
| tee baseline-bootstrap.log
cp gradle/verification-metadata.xml baseline-verification-metadata.xml
trusted_sha=$(cut -d' ' -f1 trusted-artifact.sha256)
printf 'trusted_sha=%s\n' "$trusted_sha" | tee baseline-accepted.sha256
grep -n 'policy-lib-1.0.0.jar' gradle/verification-metadata.xml
./gradlew --dependency-verification strict clean compileJava \
| tee baseline-strict.log
7. Integrity failure: mutate bytes, not coordinates
Keep the original artifact, replace it with a deterministic changed JAR, then force re-resolution.
artifact=trusted-repo/dev/academy/internal/policy-lib/1.0.0/policy-lib-1.0.0.jar
cp "$artifact" original-policy-lib-1.0.0.jar
cat > fixture-src/dev/academy/internal/PolicyLib.java <<'JAVA'
package dev.academy.internal;
public final class PolicyLib {
private PolicyLib() {}
public static String message() { return "changed-same-coordinate"; }
}
JAVA
rm -rf fixture-classes && mkdir fixture-classes
javac --release 17 -d fixture-classes fixture-src/dev/academy/internal/PolicyLib.java
jar --create --date=2026-01-01T00:00:00Z --file "$artifact" -C fixture-classes .
sha256sum "$artifact" | tee incident-actual.sha256
set +e
./gradlew --refresh-dependencies --dependency-verification strict clean compileJava \
> incident-integrity.log 2>&1
integrity_rc=$?
set -e
printf 'integrity_rc=%s\n' "$integrity_rc" | tee incident-integrity.rc
test "$integrity_rc" -ne 0
Do not regenerate verification metadata here. That would convert the incident into policy.
8. Repair integrity and prove exact identity
Restore the reviewed bytes and verify both SHA-256 and strict build behavior.
cp original-policy-lib-1.0.0.jar "$artifact"
sha256sum -c trusted-artifact.sha256
./gradlew --refresh-dependencies --dependency-verification strict clean compileJava \
| tee repaired-integrity.log
9. Repository-scope failure: make trusted origin unavailable
The alternate repository still contains the same coordinate, but exclusivity must make it ineligible.
mv trusted-repo/dev/academy/internal/policy-lib/1.0.0 \
trusted-repo/dev/academy/internal/policy-lib/1.0.0.off
set +e
./gradlew --refresh-dependencies clean compileJava > incident-scope.log 2>&1
scope_rc=$?
set -e
printf 'scope_rc=%s\n' "$scope_rc" | tee incident-scope.rc
test "$scope_rc" -ne 0
grep -Ei 'policy-lib|could not find|resolve' incident-scope.log | head -40
mv trusted-repo/dev/academy/internal/policy-lib/1.0.0.off \
trusted-repo/dev/academy/internal/policy-lib/1.0.0
./gradlew --refresh-dependencies clean compileJava | tee repaired-scope.log
10. Add Maven evidence without overstating Maven core guarantees
Produce repository sidecar digests and an optional isolated Maven
consumer. checksumPolicy=fail is useful
transport-integrity behavior, while Maven Resolver's default
remote-external algorithm order is commonly SHA-1,MD5 unless
configured otherwise; independently recorded
SHA-256/SHA-512/fingerprint evidence remains a separate review
artifact.
artifact=trusted-repo/dev/academy/internal/policy-lib/1.0.0/policy-lib-1.0.0.jar
sha256sum "$artifact" > "$artifact.sha256"
sha512sum "$artifact" > "$artifact.sha512"
sha256sum -c "$artifact.sha256" | tee maven-sha256-check.txt
sha512sum -c "$artifact.sha512" | tee maven-sha512-check.txt
# Optional when trusted Maven Wrapper is available:
# mkdir -p maven-consumer && cat > maven-consumer/pom.xml <<'POM'
# ... use the POM shown in Lesson 2 ...
# POM
# ./mvnw -f maven-consumer/pom.xml -Dmaven.repo.local="$PWD/.maven-user-repo" dependency:tree
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dev.academy.consumer</groupId>
<artifactId>maven-check</artifactId>
<version>1.0.0</version>
<repositories>
<repository>
<id>academy-internal</id>
<url>file://${project.basedir}/../trusted-repo</url>
<releases>
<enabled>true</enabled>
<checksumPolicy>fail</checksumPolicy>
</releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>dev.academy.internal</groupId>
<artifactId>policy-lib</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
11. Complete the wrapper identity record
Compare the checked-in Wrapper JAR and configured distribution
checksum to independently published Gradle data. If Maven Wrapper is
used, record distributionSha256Sum and applicable
wrapperSha256Sum from a separately verified Apache
source.
Gradle wrapper record
requested_version: 9.7.1
distribution_url: services.gradle.org/distributions/gradle-9.7.1-bin.zip
distribution_sha256: independently verified official value
wrapper_jar_sha256: independently verified official value
reviewed_by: <name/change record>
Maven wrapper record (if used)
wrapper_version: 3.3.4
maven_version: 3.9.16
distribution_sha256: independently verified Apache value
wrapper_jar_sha256: only when wrapper JAR form is present
reviewed_by: <name/change record>
12. Verification checklist
| Evidence | Pass condition |
|---|---|
| Exact identities | Gradle/Maven/JDK/Wrapper/dependency/plugin versions are recorded; no dynamic selectors in production path. |
| Trusted artifact digest | Trusted JAR digest equals baseline accepted SHA-256. |
| Strict verification | Unchanged artifact passes; changed same-coordinate artifact fails. |
| Repository scope |
Alternate repository cannot satisfy
dev.academy.internal when trusted repo is
unavailable.
|
| Wrapper trust | Expected Wrapper/distribution digests come from independent release data, not the checkout itself. |
| Maven boundary | Checksum policy and independent checksum/signature evidence are documented accurately. |
| No bypass | No accepted fix disables verification, ignores checksum errors, broadens repositories, or auto-accepts mismatched bytes. |
| Cleanup | No normal home caches, global keyrings, real credentials, or production repositories were mutated. |
13. Minimal incident handoff template
If either negative test happened unexpectedly in a real build, preserve this record before remediation:
incident_id:
first_seen_utc:
coordinate_or_plugin:
expected_version:
expected_digest_or_key:
actual_digest_or_key:
repository_name_and_url:
wrapper_version_and_digest:
jdk_version:
verification_mode:
cache_scope:
credentials_exposed_or_suspected:
containment_action:
independent_source_used_for_revalidation:
clean_room_rebuild_result:
release_artifacts_potentially_affected:
14. Cleanup and rollback
After retaining the study evidence you want, delete only the checkpoint directory. If you created a disposable GnuPG home, it is inside that directory and disappears with it.
cd ..
rm -rf ch28-checkpoint
15. What Chapter 28 adds—and the bridge to Chapter 29
You now have a trust model that survives beyond a developer workstation: exact wrapper identity, scoped repositories, immutable coordinates, checksum/signature verification, plugin trust, and evidence-preserving incident handling. Chapter 29 takes those invariants into CI/CD: ephemeral agents, cache policy, test sharding, artifact promotion, and build-once/promote-the-same-bytes workflows.
Knowledge check
Why is the integrity failure required to keep the same version?
It proves verification detects byte mutation under an apparently immutable coordinate, not just a normal version upgrade.
What exactly proves the repository-scope control?
Resolution fails when the trusted repository lacks the protected coordinate even though another declared repository contains it.
What should never be the source of the expected Wrapper checksum?
The Wrapper JAR/distribution from the same untrusted checkout being verified.
Why does the Maven lane record independent digests in addition to checksumPolicy?
Resolver checksum policy is transport/repository behavior; independent accepted digests/provenance remain a separate review control.
What is the Chapter 29 bridge?
Enforce these trust invariants on ephemeral CI agents while controlling caches, shards, and artifact promotion.
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.