Rules & effects
Every change to /git/* handling or to cgit's interface inside the window, next to what the log did afterwards. The claim stays narrow. These are observed transitions, not attribution.
The timeline
Each entry comes from data/events.csv and cites evidence in an access log, either the /git file this report reads or the server's unfiltered log. Anything without that evidence stays off this list.
Share of requests aborted
Aborted is HTTP status 0, the value abort writes to the access log. Dashed lines mark each change.
Before and after each change
Three days before each event date against three days from it, per class. Only classes with meaningful volume in either window are listed.
Outcome by class, whole window
How much of each class was served, and how much the server refused: aborted closes the connection, denied answers 403.
Fingerprints seen on /git
Caddy hashes the header names a request carries and the grammar of their values, so rotating a user-agent value leaves the token alone. The heaviest fingerprints in the log, by volume.
One rule in this window was not effective for as long as it looked. @fetchfleet_ua never matched anything. Its pattern contained spaces and no quotes, so the Caddyfile tokenizer split it into header matchers keyed on strings such as Chrome/142.0.0.0 and OS that no request carries. Traffic carrying that exact string was served
Open items as of the last reload. Two abort rules came off the day the fleet moved to address ranges. A reduced Chrome/<major>.0.0.0 string is what real Chromium sends, and android.*firefox is what real Android Firefox sends. Both matchers closed the connection on readers as well as on the fleet.
The replacement cannot use the user agent, because nothing in the request separates the fleet from a reader. The fleet's desktop string is byte-identical to a current Chrome, and it carries client hints. The block now works from addresses, with an evidence tier drawn from /24s that never served a page-rendering client, plus three ASN-wide blocks. /home/bob/jobs/forge-fleet-ranges.py rebuilds the tier from the access log each day, and the timeline above dates the change. The burst limiter still fails against a fleet that uses each address once. Until a token could name that fleet, the ranges were the only rule holding it.
A second tier arrived on 2026-10-06. A module compiled into Caddy, caddy-akin, fingerprints each request from the header names it carries and the grammar of their values, writes the token to the access log, and rejects listed tokens inside a shared akin_block snippet. The tiers answered with 403 for twelve days; on 2026-10-18 the handler gained block_abort and the /git matcher switched to the standard abort directive, so a listed token now costs a reset instead of a response.
Rotating a user-agent value does not move a token, so the fleet that cycles versions across a fixed header set collapses to a few entries that cover thousands of addresses, and the log still records what it rejected. Every token on the list came out of this log first, and the rules timeline dates both the list and its growth. A shape test joined the gates on 2026-10-09, because one sent referer made a token a person to the old test. At 200 addresses or more with 2 requests per address or fewer, the shape decides, and the human signals decide the rest.
Since 2026-11-09 the ratio is taken twice, over every request and over the requests that drew a response. A fleet the address tier already closes keeps retrying, and those retries raise the overall ratio until a half-closed fleet reads like a crowd. Either figure settles it, and the answered side is the one that catches a fleet the ranges have half closed.
On 2026-10-21 the evidence changed again. A favicon fetch no longer retires a token: the fleet had learned to ask for one, and the old test kept its heaviest grammar off the list. Only cgit's stylesheet and script count as page chrome now, and a favicon or logo is recorded as weak chrome for the audit.
The same reload added a header test inside handle /git/*. A browser sends Fetch Metadata on every request, so a caller without Sec-Fetch-Site is aborted before it reaches cgit. The test is scoped to /git; the blog, feeds, and ActivityPub endpoints keep serving non-browser clients. Plain HTTP clients no longer reach the archives; the Git smart-HTTP endpoints stay open to a git/ user agent so HTTPS clones still work.
Two exceptions joined that test on 2026-10-25. Both sit inside the /git handler, and both are handles: abort sorts after handle, so a matched handle answers first. A user agent carrying Safari/604.1 Brave and Sec-Fetch-Site now proxies straight to cgit. The fingerprint tier had closed the connection on that reader, whose token also carries hundreds of preview-fetching addresses. That token never separated the reader from the fleet sharing it.
Fediverse unfurlers receive a fixed card carrying Open Graph tags instead. Every path is covered except the clone endpoints, because an unfurler sends no Fetch Metadata. This test had been resetting every pasted /git link, which is why the card exists. Nothing in the card comes out of cgit, so the grammar buys a page holding no artifact. A 200 still comes back, and the served rows count cards rather than archives.