Skip to content

Combine turnstile and rack-attack#410

Open
JPrevost wants to merge 14 commits into
mainfrom
turnstile-plus-rackattack
Open

Combine turnstile and rack-attack#410
JPrevost wants to merge 14 commits into
mainfrom
turnstile-plus-rackattack

Conversation

@JPrevost

Copy link
Copy Markdown
Member

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.

@coveralls

coveralls commented Jun 22, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 28115565855

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+0.003%) to 98.356%

Details

  • Coverage increased (+0.003%) from the base build.
  • Patch coverage: 3 of 3 lines across 1 file are fully covered (100%).
  • 1 coverage regression across 1 file.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

1 previously-covered line in 1 file lost coverage.

File Lines Losing Coverage Coverage
app/models/normalize_primo_record.rb 1 99.33%

Coverage Stats

Coverage Status
Relevant Lines: 1521
Covered Lines: 1496
Line Coverage: 98.36%
Coverage Strength: 70.86 hits per line

💛 - Coveralls

@mitlib mitlib temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 22, 2026 16:48 Inactive
@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 22, 2026 17:24 Inactive
@qltysh

qltysh Bot commented Jun 22, 2026

Copy link
Copy Markdown

❌ 2 blocking issues (2 total)

Tool Category Rule Count
rubocop Lint Useless assignment to variable - default\_grace\_period. 1
rubocop Lint Use normalcase for method name numbers. 1

Comment thread app/controllers/turnstile_controller.rb Outdated
@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 22, 2026 18:06 Inactive
@jazairi jazairi self-assigned this Jun 22, 2026
@JPrevost JPrevost requested a review from Copilot June 22, 2026 18:28

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

Adds Rack::Attack-based protections for the expensive /results and /record endpoints and ties throttling into the existing Turnstile flow so legitimate users can recover from throttling via challenge verification.

Changes:

  • Introduces a global RPS throttle plus a stricter per-IP throttle for /results and /record.
  • Overrides Rack::Attack throttled responses on those endpoints to redirect to the Turnstile challenge instead of returning 429.
  • After successful Turnstile verification, writes a short-lived “verified” cache key intended to suppress re-challenges for that IP.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
README.md Documents new ENV knobs for the results/record throttles and Turnstile grace period.
config/initializers/rack_attack.rb Adds new throttles and custom throttled response logic for /results and /record.
app/controllers/turnstile_controller.rb Writes a cache-based grace-period marker after successful Turnstile verification.

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

Comment thread config/initializers/rack_attack.rb Outdated
Comment thread config/initializers/rack_attack.rb
Comment thread README.md Outdated
Comment thread app/controllers/turnstile_controller.rb Outdated
Comment thread app/controllers/turnstile_controller.rb Outdated
Comment thread app/controllers/turnstile_controller.rb Outdated
Comment thread config/initializers/rack_attack.rb Outdated
Comment thread config/initializers/rack_attack.rb Outdated

@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.

I think all of Copilot's suggested feedback is worth implementing, particularly the fix for the potential feedback loop. Once that is in place, this is ready to merge from my perspective.

I was pondering adding unit tests for the grace period and throttle_response logic, but I couldn't figure a good way to do so that wouldn't just be testing implementation details. In this case, I think manual testing is sufficient. Anything more would likely have to be a system test, which would be new to us and well out of scope of this urgent change.

Also fixes up docs and adds tests
@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 22, 2026 20:12 Inactive
@JPrevost JPrevost requested a review from Copilot June 22, 2026 20:13

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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

Comment thread config/initializers/rack_attack.rb
Comment thread app/controllers/turnstile_controller.rb Outdated
Comment thread test/controllers/turnstile_controller_test.rb
Comment thread test/controllers/turnstile_controller_test.rb
Also more tests and docs fixups
@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 22, 2026 20:34 Inactive
@JPrevost JPrevost requested a review from Copilot June 22, 2026 20:35
@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 22, 2026 20:41 Inactive
@JPrevost JPrevost requested a review from Copilot June 22, 2026 20:42

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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment thread config/initializers/rack_attack.rb
Comment thread config/initializers/rack_attack.rb Outdated
Comment thread app/controllers/turnstile_controller.rb Outdated
Comment on lines +15 to +17
cache_key = "turnstile_verified:#{request.ip}"
grace_period = ENV.fetch('TURNSTILE_GRACE_PERIOD') { 15 }.to_i.minutes
Rails.cache.write(cache_key, true, expires_in: grace_period)
@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 22, 2026 20:51 Inactive
@JPrevost JPrevost requested a review from Copilot June 22, 2026 20:52

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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment thread README.md
Comment on lines 137 to 138
- `REQUESTS_PER_PERIOD` - number of requests that can be made for general throttles per `REQUEST_PERIOD`
- `REQUEST_PERIOD` - time in minutes used along with `REQUESTS_PER_PERIOD`
Comment thread README.md
- `RESULTS_THROTTLE_PERIOD` - time in minutes for `/results` and `/record` endpoint throttle (default 1 minute). Throttled requests are redirected to Turnstile for verification.
- `TURNSTILE_GRACE_PERIOD` - time in minutes that an IP is whitelisted from throttling after successfully passing Turnstile verification (default 15 minutes). This prevents users from being re-challenged repeatedly.
- `REDIRECT_REQUESTS_PER_PERIOD`- number of requests that can be made that the query string starts with our legacy redirect parameter to throttle per `REQUEST_PERIOD`
- `REDIRECT_REQUEST_PERIOD`- time in minutes used along with `REDIRECT_REQUEST_PERIOD`
Comment on lines +125 to +127
# IMPORTANT: Only redirect if the matched throttle is one that has a grace period cache check
# (results/global or req/ip/results). Other throttles (req/ip, etc.) don't have grace period
# exemptions, so redirecting would create an infinite loop.
@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 22, 2026 21:17 Inactive
@JPrevost JPrevost requested a review from Copilot June 22, 2026 21:20

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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

Comment thread app/controllers/turnstile_controller.rb Outdated
Comment on lines +15 to +17
cache_key = "turnstile_verified:#{request.ip}"
grace_period = ENV.fetch('TURNSTILE_GRACE_PERIOD') { 15 }.to_i.minutes
Rails.cache.write(cache_key, true, expires_in: grace_period)
Comment on lines +51 to +54
post turnstile_verify_path,
params: { 'cf-turnstile-response' => 'mocked', return_to: '/results?q=test' },
headers: { 'REMOTE_ADDR' => ip }

Comment on lines +67 to +70
post turnstile_verify_path,
params: { 'cf-turnstile-response' => 'mocked', return_to: '/results?q=test' },
headers: { 'REMOTE_ADDR' => ip }

Comment on lines +90 to +93
post turnstile_verify_path,
params: { 'cf-turnstile-response' => 'mocked', return_to: '/results?q=test' },
headers: { 'REMOTE_ADDR' => ip }

Comment on lines +112 to +115
post turnstile_verify_path,
params: { 'cf-turnstile-response' => 'mocked', return_to: '/results?q=test' },
headers: { 'REMOTE_ADDR' => ip1 }

Comment on lines +117 to +120
post turnstile_verify_path,
params: { 'cf-turnstile-response' => 'mocked', return_to: '/results?q=test' },
headers: { 'REMOTE_ADDR' => ip2 }

Comment on lines +134 to +137
post turnstile_verify_path,
params: { return_to: '/results?q=test' },
headers: { 'REMOTE_ADDR' => ip }

@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 24, 2026 14:35 Inactive

test 'verify applies default grace period when TURNSTILE_GRACE_PERIOD env var not set' do
with_bot_detection_enabled do
default_grace_period = 15 # minutes (the default from the controller)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Useless assignment to variable - default_grace_period. [rubocop:Lint/UselessAssignment]

require 'test_helper'

class RackAttackBlocklistTest < ActionDispatch::IntegrationTest
def test_blocked_user_agent_returns_403

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Use normalcase for method name numbers. [rubocop:Naming/VariableNumber]

@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 24, 2026 14:44 Inactive
@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 24, 2026 15:41 Inactive
@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 24, 2026 16:09 Inactive
@JPrevost JPrevost temporarily deployed to timdex-ui-pi-turnstile--p4o43h June 24, 2026 17:01 Inactive
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