Java
SlowShield serves Maven repositories at https://slowshield.example.com/maven/, for Maven,
Gradle, sbt and Coursier, and so for Java, Kotlin and Scala projects. Builds keep their dependencies and plugins as
they are; only where they come from changes.
| Path | Serves | For |
|---|---|---|
/maven/all/ | Google's groups from Google Maven, everything else from Maven Central | Maven's mirror, sbt, Coursier |
/maven/central/ | Maven Central | Gradle mavenCentral() |
/maven/google/ | Google Maven | Gradle google() |
/maven/gradle-plugins/ | the Gradle Plugin Portal | Gradle gradlePluginPortal() and plugin resolution |
/maven/<id>/ | a repository your administrator adds (JitPack, Confluent, …) | anything else |
Set it up
Maven and Gradle don't read environment variables for this: each needs a file in your home directory, which covers every project on the machine.
Maven
~/.m2/settings.xml
<settings>
<mirrors>
<mirror>
<id>slowshield</id>
<!-- private repositories stay direct: *,!their-id -->
<mirrorOf>*</mirrorOf>
<url>https://slowshield.example.com/maven/all/</url>
</mirror>
</mirrors>
</settings>Gradle
~/.gradle/init.d/slowshield.init.gradle
def slowshield = [
'https://repo.maven.apache.org/maven2': 'https://slowshield.example.com/maven/central/',
'https://repo1.maven.org/maven2': 'https://slowshield.example.com/maven/central/',
'https://dl.google.com/dl/android/maven2': 'https://slowshield.example.com/maven/google/',
'https://plugins.gradle.org/m2': 'https://slowshield.example.com/maven/gradle-plugins/',
]
def rewrite = { repo ->
if (repo instanceof MavenArtifactRepository) {
def to = slowshield[repo.url.toString().replaceAll('/$', '')]
if (to) { repo.url = new URI(to) }
}
}
beforeSettings { s -> s.pluginManagement.repositories.all(rewrite) }
settingsEvaluated { s ->
s.pluginManagement.repositories.all(rewrite)
s.dependencyResolutionManagement.repositories.all(rewrite)
}
allprojects { p ->
p.buildscript.repositories.all(rewrite)
p.repositories.all(rewrite)
}sbt
~/.sbt/repositories
[repositories]
local
slowshield: https://slowshield.example.com/maven/all/environment
export SBT_OPTS="-Dsbt.override.build.repos=true $SBT_OPTS"Coursier
environment
export COURSIER_REPOSITORIES="ivy2Local|https://slowshield.example.com/maven/all/"What changes
- The publish time is each file's
Last-Modifiedon the repository (when the repository stored it), or when SlowShield first saw the version listed, whichever is earlier. - Version ranges and
latestpick the newest version that is old enough: newer ones are left out ofmaven-metadata.xml, and<latest>and<release>are recomputed. - A pin that is too new fails with
425 Too Early. Maven and Gradle only show an error's status line, never its body, and a403would read like a credentials problem:Maven asks again on every build (it caches aCould not transfer artifact org.example:lib:jar:1.4.0 from/to slowshield (https://slowshield.example.com/maven/all/): status code: 425, reason phrase: Too Early (425)404, not a425), so the first build after the hold ends works without-U. - Brand-new artifacts none of whose versions is old enough are held too: Maven doesn't fail open, because brand-new artifacts are the realistic attack (typosquats, dependency confusion).
- Malware and tampering are refused with
451. Every file is checked against the repository's checksums and the fingerprint SlowShield recorded the first time.
The second layer
Maven, Gradle, sbt and Coursier have no release-age setting, so SlowShield is the only layer.
In Docker and CI
Write the mirror into the build container's settings.xml. With a build argument, the same Dockerfile
still builds without SlowShield when the argument is empty:
FROM maven:3.9-eclipse-temurin-21 AS build
ARG MAVEN_MIRROR=https://slowshield.example.com/maven/all/
RUN if [ -n "$MAVEN_MIRROR" ]; then mkdir -p /root/.m2 && printf '%s' \
"<settings><mirrors><mirror><id>slowshield</id><mirrorOf>*</mirrorOf><url>$MAVEN_MIRROR</url></mirror></mirrors></settings>" \
> /root/.m2/settings.xml; fi
WORKDIR /src
COPY pom.xml .
COPY src src
RUN mvn -B packageThis one mirror also covers Maven's own plugins: a test build of a small project fetched 107 artifacts through
SlowShield, plugins included. For Gradle, copy the init script above to
/root/.gradle/init.d/slowshield.gradle. Building images covers the
base image too, and how to check that nothing in a build goes around SlowShield.
Limits
- The Gradle wrapper downloads Gradle itself from services.gradle.org, which SlowShield doesn't serve. Use a
Gradle that is already installed, such as the one in the official
gradleimages, where only SlowShield is reachable. - Private repositories stay direct with
<mirrorOf>*,!their-id</mirrorOf>. - Plain HTTP (a local test instance): Maven blocks
http://repositories; the Setup page'ssettings.xmloverrides that block with the mirror idmaven-default-http-blocker. - Block repo1.maven.org, repo.maven.apache.org, dl.google.com and plugins.gradle.org at the firewall for machines that should only use SlowShield.
For agents: this page as Markdown · llms.txt · the SlowShield skill