Skip to content

Apply cooldown to Maven - #573

Merged
andrew merged 6 commits into
git-pkgs:mainfrom
simonchrz:feat/maven-cooldown
Oct 24, 2026
Merged

andrew merged 6 commits into
git-pkgs:mainfrom
simonchrz:feat/maven-cooldown

Conversation

@simonchrz

@simonchrz simonchrz commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Maven part of #132, along the lines agreed there.

With cooldown on:

  • A version's publish time is the Last-Modified of its .pom, stored in the versions table once per version, as npm and Go do. On Maven Central it is the deploy time. When the .pom or Gradle .module itself is downloaded, which clients do before a version's other files, the time comes from that response, so a build of pinned versions costs no extra requests. Otherwise it is read with a HEAD request that names the proxy in its User-Agent. A .pom or .module refused this way is still cached, so it counts toward max_size before the version leaves cooldown. Only a 2xx response is used. A missing .pom lets the version through; any other failure gives 502 instead of letting it through, or 503 with the upstream's Retry-After when Central rate-limits the proxy.
  • To keep requests to Central down, filtering maven-metadata.xml only checks versions that can still be in cooldown. If <lastUpdated> is older than the window, nothing is checked. Otherwise the proxy walks the list from the end and stops at the first version that is old enough, so usually one or two HEAD requests per artifact, once.
  • Versions in cooldown are left out of maven-metadata.xml. <latest> and <release> move to the last versions left, and the metadata's .md5/.sha1/.sha256/.sha512 are computed from what is served. The document is edited in place, so the rest stays as upstream sent it, and served like other ecosystems' rewritten metadata, with gzip and the stale warning.
  • Any file of a version in cooldown gets 403, as for a version on the denylist.
  • Repositories under upstream.maven_repositories store their times under a repository_url qualifier, so they don't share times with Central.
  • SNAPSHOT versions are left alone.

The caveats are in docs/configuration.md next to the Go one: Nexus/Artifactory may report when they cached the file; on Google's mirror Last-Modified is when the file reached the bucket (the proxy doesn't read x-goog-meta-last-modified); and since Central lists versions in version order, a backport to an older line can stay listed while in cooldown, though its files are refused.

Checked against Maven Central with a 10-day cooldown:

  • Metadata for jackson-databind: 2.22.4 (17 October) was left out, <latest> and <release> became 2.22.3, the served .sha1 matched, and the 2.22.4 .pom got 403. That took two HEAD requests; a second metadata request took none.
  • Maven 3.9 in a container, through the proxy: a project pinned to jackson-databind 2.22.4 fails with 403 on the .pom; 2.22.3 resolves.

A first version read each pinned version's time with a HEAD before its download. A fresh Maven build through it sent about 150 requests to Central in five seconds and got a 429 with a 30-minute Retry-After; reading the time from the .pom download removes those HEADs. I couldn't repeat the full build after that without risking another block, so the version-range and plugin cases are covered by the unit tests only.

Cooldown needs to read maven-metadata.xml before serving it, with the
same Gradle Plugin Portal fallback and error handling.
A version's publish time is the Last-Modified of its .pom, read with a
HEAD request once per version and stored in the versions table. Named
repositories store theirs under a repository_url qualifier.

Versions in cooldown are left out of maven-metadata.xml, <latest> and
<release> are moved to the last versions left, and the metadata's
checksum files are computed from what is served. Files of a version in
cooldown get 403. Only versions that can still be in cooldown are
checked: none when <lastUpdated> is older than the window, otherwise
from the end of the list back to the first old enough version.

Part of git-pkgs#132.
Includes the caveats from git-pkgs#132: Nexus and Artifactory dates, Google's
Maven Central mirror, version-ordered lists and SNAPSHOTs.
A build fetching pinned versions never reads maven-metadata.xml, so each
version's time was read with a HEAD before its download, roughly
doubling the requests to Maven Central, which answered a fresh build
with 429 and a 30-minute Retry-After. The .pom's own response now gives
the time; Maven fetches the .pom before a version's other files, so
their check reads the stored time. A HEAD is only sent for a .pom served
from the cache or a file requested before its .pom.

A 429 on that HEAD now answers 503 with the upstream's Retry-After, as
other held upstream requests do, instead of 502.
- Name the proxy in the User-Agent of publish-time HEAD requests, as its
  artifact downloads do, rather than Go's default, which Maven Central
  asks tools not to send.
- Take a version's time from its Gradle .module as well as its .pom, so
  either one fetched first carries it.
- Serve filtered metadata through the shared rewrite path, with gzip and
  the stale warning like the other ecosystems.
- Say in the docs that a refused .pom or .module is still cached.
@andrew
andrew merged commit 4a1e4fe into git-pkgs:main Oct 24, 2026
6 checks passed
@simonchrz simonchrz added the breaking Changes behaviour users rely on; needs a minor release label Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Changes behaviour users rely on; needs a minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants