Skip to content

Turnstile verified users bypass req/ip throttling#433

Open
JPrevost wants to merge 1 commit into
mainfrom
use-647
Open

Turnstile verified users bypass req/ip throttling#433
JPrevost wants to merge 1 commit into
mainfrom
use-647

Conversation

@JPrevost

@JPrevost JPrevost commented Jul 16, 2026

Copy link
Copy Markdown
Member

Why are these changes being introduced:

  • Actual users were being blocked when switching tabs rapidly
  • We don't want to increase the throttle limit for everyone, so we will bypass the throttle for verified users

Relevant ticket(s):

How does this address that need:

  • Abstracts list of throttles that allow turnstile bypass
  • Adds a check for turnstile verified users to bypass the req/ip and previous throttles they could bypass updated to use new abstraction
  • Adjusts which routes bypass the req/ip throttle using new abstraction (static pages don't need throttling)

Document any side effects to this change:

  • Bots or absusive users that can pass the turnstile check now have more leeway to make requests

Developer

Accessibility
  • ANDI or WAVE has been run in accordance to our guide.
  • This PR contains no changes to the view layer.
  • New issues flagged by ANDI or WAVE have been resolved.
  • New issues flagged by ANDI or WAVE have been ticketed (link in the Pull Request details above).
  • No new accessibility issues have been flagged.
New ENV
  • All new ENV is documented in README.
  • All new ENV has been added to Heroku Pipeline, Staging and Prod.
  • ENV has not changed.
Approval beyond code review
  • UXWS/stakeholder approval has been confirmed.
  • UXWS/stakeholder review will be completed retroactively.
  • UXWS/stakeholder review is not needed.
Additional context needed to review

E.g., if the PR includes updated dependencies and/or data
migration, or how to confirm the feature is working.

Code Reviewer

Code
  • I have confirmed that the code works as intended.
  • Any CodeClimate issues have been fixed or confirmed as
    added technical debt.
Documentation
  • The commit message is clear and follows our guidelines
    (not just this pull request message).
  • The documentation has been updated or is unnecessary.
  • New dependencies are appropriate or there were no changes.
Testing
  • There are appropriate tests covering any new functionality.
  • No additional test coverage is required.

Why are these changes being introduced:

* Actual users were being blocked when switching tabs rapidly
* We don't want to increase the throttle limit for everyone, so we will
  bypass the throttle for verified users

Relevant ticket(s):

* https://mitlibraries.atlassian.net/browse/USE-647

How does this address that need:

* Abstracts list of throttles that allow turnstile bypass
* Adds a check for turnstile verified users to bypass the req/ip and
  previous throttles they could bypass updated to use new abstraction
* Adjusts which routes bypass the req/ip throttle using new abstraction
 (static pages don't need throttling)

Document any side effects to this change:

* Bots or absusive users that can pass the turnstile check now have
  more leeway to make requests
@qltysh

qltysh Bot commented Jul 16, 2026

Copy link
Copy Markdown

❌ 5 blocking issues (5 total)

Tool Category Rule Count
rubocop Style Line is too long. [132/120] 2
rubocop Style Do not use space inside array brackets. 2
rubocop Style Incorrect formatting, autoformat by running qlty fmt. 1

@qltysh one-click actions:

  • Auto-fix formatting (qlty fmt && git push)

test 'turnstile_grace_cookie_valid? validates signed future expiration cookie' do
refute Rack::Attack.turnstile_grace_cookie_valid?(rack_request_with_turnstile_cookie)
refute Rack::Attack.turnstile_grace_cookie_valid?(rack_request_with_turnstile_cookie('tampered-value'))
refute Rack::Attack.turnstile_grace_cookie_valid?(rack_request_with_turnstile_cookie(turnstile_cookie(Time.current - 1.minute)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [132/120] [rubocop:Layout/LineLength]

refute Rack::Attack.turnstile_grace_cookie_valid?(rack_request_with_turnstile_cookie('tampered-value'))
refute Rack::Attack.turnstile_grace_cookie_valid?(rack_request_with_turnstile_cookie(turnstile_cookie(Time.current - 1.minute)))

assert Rack::Attack.turnstile_grace_cookie_valid?(rack_request_with_turnstile_cookie(turnstile_cookie(Time.current + 15.minutes)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [134/120] [rubocop:Layout/LineLength]

test 'free paths do not count against general req/ip throttle' do
limit = ENV.fetch('REQUESTS_PER_PERIOD', 100).to_i

[ '/', '/about', '/about-natural-language-search' ].each do |path|

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 2 issues:

1. Do not use space inside array brackets. [rubocop:Layout/SpaceInsideArrayLiteralBrackets]


2. Do not use space inside array brackets. [rubocop:Layout/SpaceInsideArrayLiteralBrackets]

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 29520214549

Coverage remained the same at 98.384%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1547
Covered Lines: 1522
Line Coverage: 98.38%
Coverage Strength: 83.85 hits per line

💛 - Coveralls

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts Rack::Attack throttling behavior so that users who have successfully passed the Cloudflare Turnstile challenge (and have a valid grace-period cookie) can bypass additional throttles—specifically the general per-IP request throttle—reducing false-positive blocking when rapidly switching tabs.

Changes:

  • Centralizes “Turnstile-challenge eligible” throttles and “free/cheap” paths in constants, with helper methods for cookie validation and path exclusion.
  • Extends the general req/ip throttle to honor the Turnstile grace cookie and to exclude additional cheap/static routes from counting against the per-IP limit.
  • Updates integration tests to cover the new helper methods and updated redirect/429 behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
config/initializers/rack_attack.rb Adds constants/helpers and updates throttles/responder to allow Turnstile-verified bypass and free-path exclusions.
test/integration/rack_attack_throttle_429_test.rb Updates responder behavior tests (redirect vs 429) to match new throttle routing.
test/integration/rack_attack_cookie_bypass_test.rb Adds/extends integration coverage for cookie validation, free-path detection, and req/ip bypass behavior.
Comments suppressed due to low confidence (1)

config/initializers/rack_attack.rb:211

  • throttled_responder redirects for any request that hits a throttle in TURNSTILE_CHALLENGE_THROTTLES (including req/ip), regardless of request type. This can return the full Turnstile HTML page to non-browser/XHR callers (e.g., /libkey and /browzine are fetched via fetch()), which can break those clients. Consider only redirecting for interactive HTML GET navigations and returning 429 for XHR/API requests.
    if TURNSTILE_CHALLENGE_THROTTLES.include?(matched_throttle)
      # Redirect to Turnstile challenge
      return_to = "#{request.path_info}?#{request.query_string}".gsub(/\?$/, '')
      [ 302,
        { 'Location' => "/turnstile?return_to=#{ERB::Util.url_encode(return_to)}" },

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +24 to 26
test 'req/ip throttle redirects to Turnstile' do
env = build_mock_env(path: '/about', matched_throttle: 'req/ip')

@JPrevost
JPrevost requested a review from jazairi July 16, 2026 17:42

@jazairi jazairi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a reasonable compromise. The extra leeway is worth keeping an eye on, but I think Turnstile is effective enough that it shouldn't matter too much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants