Anchor the docs redirect patterns, so they match one path only - #668
Merged
Merged
Conversation
Without a trailing $, a pattern matches at any depth, so every retired path has an infinite URL space beneath it that answers 302 rather than 404. A crawler that resolves a target page's relative links against the requested URL appends a segment, is redirected again, and never terminates. That accounts for 7,388,812 of the 15,569,216 requests in the retained logs, between 333,000 and 689,000 a day, and the share has grown from 45% to 52%. Rate limiting cannot help: the crawler averages 75 requests per 10 seconds, below any threshold that a WordPress admin page load stays under. Of the deeper requests, 137 are not loop artefacts. None is a URL that ever existed: what.md has only been under primer/, and design.md, consortia.md and pre-qualification.md only under guidance/ — never under getting_started/ or implementation/, where they were requested. The optional trailing slash is unchanged, and the anchor is on the URL-path, so query strings still match and are appended to the target. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148VmvbJ2LcshwztmwuKRoU
jpmckinney
force-pushed
the
anchor-docs-redirects
branch
7 times, most recently
from
September 21, 2026 16:37
8b4ade1 to
5bbe34e
Compare
The Free plan keeps 24 hours of Security Events and samples the dashboard's logs, so a longer window has to be collected: this prints JSON lines, one object per event, across every zone, to redirect to a file from cron. Hours beyond 24 return nothing more. It warns rather than paginates when a zone reaches the limit. 10,000 is accepted, and a day's events are about half that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148VmvbJ2LcshwztmwuKRoU
jpmckinney
force-pushed
the
anchor-docs-redirects
branch
from
September 21, 2026 16:43
5bbe34e to
b4e0dd3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RedirectMatchpatterns indocs.conf.includeend with/?and no$, so they match at any depth. Every retired path therefore has an infinite URL space beneath it that answers302instead of404:A crawler that resolves the target page's relative links against the requested URL appends a segment, is redirected again, and never terminates. One client has produced 550+ distinct paths up to 24 segments deep, with
merging,hostingandserializationrepeating.Scale
Between 333,000 and 689,000 a day, and the share has grown from 45% to 52% over the last week.
Rate limiting cannot solve this. The crawler averages about 75 requests per 10 seconds — below any threshold a WordPress block-editor page load stays under, so the abusive client is slower than the legitimate one. The traffic is proxied (every peer IP is a Cloudflare edge), so this isn't origin circumvention either.
What changes
60 template patterns, expanding to 324 rendered rules.
/?becomes/?$, and nothing else:dirhtmlmakes/path/canonical while old links use both forms. Verified on rules that are already anchored —/latestand/latest/both redirect correctly./latest?foo=bar→/latest/en/?foo=bar.Nothing regresses
Of the deeper requests, 137 are not loop artefacts. Checking each against
open-contracting/standard's history, none is a URL that ever existed:None ever lived under
getting_started/orimplementation/, where they were requested — they are the same loop one level down, manufactured from sibling links on the redirect target.No rules were deleted. Three deletion criteria were tested and none was met: no rule has a missing target (the 15 flagged are
$1backreferences and two-hop chains), none is unreachable (the "decreasing order of specificity" ordering holds across all 364), and 216 are never requested in the retained window — which is a link-rot judgement, not a technical one.Also here
manage.py cloudflare eventsprints firewall events as JSON lines. The Free plan retains Security Events for 24 hours and samples the dashboard's logs, so a longer window has to be collected; this is what produced the evidence above. It pages by walking the upper bound backwards, since the dataset has no cursor.Related
Bears on #266, which asks whether the redirects are in fact accessed: 216 of 364 rules have never been requested, and the 148 that have serve 2,726 requests — 0.017% of traffic. Of those, 124 rules are in-docs page moves that Sphinx or ReadTheDocs redirects could own, and 24 need hosting-level config.
🤖 Generated with Claude Code
https://claude.ai/code/session_0148VmvbJ2LcshwztmwuKRoU