Repository navigation
Description
The chapter catch-all route (config/routes.rb:206, get ':id' => 'chapter#show', as: :chapter) matches every root-level path that no other route claims, and sends all of it — including scanner junk — into the controller and a database query. Adding a slug-format constraint would 404 the overwhelming majority of that traffic at the router, with zero effect on real chapter pages.
Evidence (2026-09-16, canonical log archive)
In one day, ChapterController served 4,218 requests — 7× more than any other controller. 91% were 404s:
| Traffic type | Requests | Examples |
|---|---|---|
| Sensitive-file probes | ~2,986 | /key.pem, /id_rsa, /rclone.conf, /docker-compose.yml, /secrets.env |
| Vite RSC CVE probe | 250 | /__vite_rsc_findSourceMapURL |
| Targeted probe | 142 | /z9x8c7v6b5-debug-trigger-codebar.io |
| Real chapter pages | ~370 | /shanghai, /nairobi, /brighton, /oslo |
The history (found via git archaeology): the catch-all dates to c322c5af (2014, "add local chapter page"), where it was a deliberate product choice — chapter pages at codebar.io/london rather than codebar.io/chapters/london. The URL design is still right; the unconstrained implementation predates the era of mass scanner traffic.
Proposed change
get ':id' => 'chapter#show', as: :chapter, constraints: { id: /\A[a-z0-9-]+\z/ }Chapter slugs are generated with name.parameterize (app/models/chapter.rb:78), which produces only lowercase letters, digits, and hyphens, so this constraint matches every real slug and nothing else. Paths containing dots, uppercase letters, or other characters — most of the probe traffic — would 404 at the router without touching ChapterController or the database.
Notes
- This is complementary to PR feat: block scanner junk at the nginx edge #2884 (nginx edge block), which filters the observed probe classes before nginx routing. The constraint is structural: it also covers whatever the edge list misses, such as any path with a dot or uppercase character.
- The remaining junk that still matches the constraint (e.g.
/backup,/rclone) continues to 404 in-app as today; the edge block handles the known ones. - Testing: add a routing spec asserting the constraint (real slugs route; dotted/uppercase paths do not match). The nginx CI smoke matrix from feat: block scanner junk at the nginx edge #2884 is unaffected.
Full 2026-09-16 breakdown
- 4,218 canonical
ChapterController#showrequests vs 618 for the next controller (EventsController) - 3,838 of 4,218 (91%) were 404s
- ~2,986 sensitive-file probes across 51 distinct paths (SSH keys, cloud configs,
.envvariants, service-account JSONs, WordPress paths) - 250
/__vite_rsc_findSourceMapURLprobes (known Vite RSC source-map disclosure CVE scan) - 142
/z9x8c7v6b5-debug-trigger-codebar.iotargeted probes - Sources are distributed (each IP ~4 requests via Cloudflare); bursts of 100–190 req/min
- Genuine chapter traffic:
/shanghai147,/nairobi143,/brighton17,/oslo11 — all 200s - Related: feat: block scanner junk at the nginx edge #2884 adds the nginx edge block and CI smoke tests for the same traffic
Activity
fix: constrain the chapter catch-all route to slug format
fix: constrain the chapter catch-all route to the slug alphabet
fix: constrain the chapter catch-all route to the slug alphabet