From b5aa8ad5c1182c0c44d23aa55fd3fa3dfceb82de Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sat, 19 Sep 2026 10:31:59 +0200 Subject: [PATCH] fix: constrain the chapter catch-all route to the slug alphabet Chapter slugs come from name.parameterize, which produces lowercase letters, digits, hyphens, and underscores (parameterize preserves underscores via its gsub). Constraining the catch-all (:id => 'chapter#show') to that alphabet makes paths with dots, uppercase, or other characters 404 at the router instead of reaching ChapterController and the database. format: false stops Rails from consuming a trailing .pem as an optional format segment. Most of this traffic is scanner junk: on 2026-09-16, 91% of the 4,218 ChapterController requests were 404s, mostly sensitive-file probes. Underscore paths still route and 404 in-app with the branded page. Production slug audit (2026-09-19): 54 chapters, 0 slugs outside [a-z0-9_-]+. Closes #2891 --- config/routes.rb | 2 +- spec/routing/chapter_routing_spec.rb | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 spec/routing/chapter_routing_spec.rb diff --git a/config/routes.rb b/config/routes.rb index 1703ba314..6c524fa25 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -201,7 +201,7 @@ get 'donate' => 'pages#show', id: 'donate' get 'codebar-stories-podcast' => 'pages#show', id: 'codebar-stories-podcast' - get ':id' => 'chapter#show', as: :chapter + get ':id' => 'chapter#show', as: :chapter, format: false, constraints: { id: /[a-z0-9_-]+/ } # Redirects get '/my/jobs/new', to: redirect('https://jobs.codebar.io/my/jobs/new') diff --git a/spec/routing/chapter_routing_spec.rb b/spec/routing/chapter_routing_spec.rb new file mode 100644 index 000000000..409ed61ec --- /dev/null +++ b/spec/routing/chapter_routing_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'chapter catch-all route' do + it 'routes a chapter slug to chapter#show' do + expect(get: '/london').to route_to(controller: 'chapter', action: 'show', id: 'london') + end + + # Chapter#set_slug builds slugs with name.parameterize, which produces + # lowercase letters, digits, hyphens, and underscores. + it 'routes every character class the slug generator can produce' do + expect(get: '/123').to route_to(controller: 'chapter', action: 'show', id: '123') + expect(get: '/south-london').to route_to(controller: 'chapter', action: 'show', id: 'south-london') + expect(get: '/spring_wildcats').to route_to(controller: 'chapter', action: 'show', id: 'spring_wildcats') + end + + it 'does not route paths containing dots' do + expect(get: '/key.pem').not_to be_routable + end + + it 'does not route paths containing uppercase characters' do + expect(get: '/Shanghai').not_to be_routable + end +end