Problem
Scout error group 91205 (production) has accumulated 7,400+ occurrences (~34/day) of:
ActionView::Template::Error: expected :page >= 1; got -11
on GET /organizations/hardrock/courses/hardrock-100-counter-clockwise/best_efforts?page=-11'%20UNION%20ALL%20SELECT...
The traffic is SQL-injection scanner bots probing the page param. The injection itself accomplishes nothing — to_i reduces the payload to -11 — but pagy raises on a non-positive page during render, so every probe becomes a 500 and a Scout error, burying real errors in noise.
Cause
PreparedParams#page (app/controllers/concerns/prepared_params.rb:54-57) guards zero but not negatives:
defpageresult=params[:page]&.to_i || FIRST_PAGEresult.zero? ? FIRST_PAGE : resultend
"-11' UNION ..." → -11, which passes through to CourseBestEffortsDisplay → pagy_countless_from_scope(page: page) → pagy raises.
Fix
Clamp to the first page for any value below 1:
result < 1 ? FIRST_PAGE : result
A bot asking for page -11 gets page 1 (a 200), matching the existing zero behavior. Worth a quick sweep for other params[:page] consumers that bypass PreparedParams (e.g. anything calling pagy with a raw param) so all paginated views get the same guard.
🤖 Generated with Claude Code
Problem
Scout error group 91205 (production) has accumulated 7,400+ occurrences (~34/day) of:
on
GET /organizations/hardrock/courses/hardrock-100-counter-clockwise/best_efforts?page=-11'%20UNION%20ALL%20SELECT...The traffic is SQL-injection scanner bots probing the
pageparam. The injection itself accomplishes nothing —to_ireduces the payload to-11— but pagy raises on a non-positive page during render, so every probe becomes a 500 and a Scout error, burying real errors in noise.Cause
PreparedParams#page(app/controllers/concerns/prepared_params.rb:54-57) guards zero but not negatives:"-11' UNION ..."→-11, which passes through toCourseBestEffortsDisplay→pagy_countless_from_scope(page: page)→ pagy raises.Fix
Clamp to the first page for any value below 1:
A bot asking for page -11 gets page 1 (a 200), matching the existing zero behavior. Worth a quick sweep for other
params[:page]consumers that bypassPreparedParams(e.g. anything callingpagywith a raw param) so all paginated views get the same guard.🤖 Generated with Claude Code