Skip to content

fix: rejected requests should not modify ratelimit state#4080

Merged
chronark merged 8 commits intomainfrom
10-07-test_rejected_requests_should_not_modify_ratelimit_state
Oct 9, 2025
Merged

fix: rejected requests should not modify ratelimit state#4080
chronark merged 8 commits intomainfrom
10-07-test_rejected_requests_should_not_modify_ratelimit_state

Conversation

@chronark
Copy link
Collaborator

@chronark chronark commented Oct 7, 2025

What does this PR do?

Fixes a critical bug where multiple rate limits cause incorrect counter decrements when one limit is triggered. When a key has multiple rate limits (e.g., 12/minute and 200/month), and the first limit gets checked and incremented, then the second limit is triggered, the first limit's counter remains incremented even though the request was rejected.

This PR implements an atomic batch processing approach for rate limits, ensuring that when any rate limit check fails, none of the counters are incremented. This prevents counter leaks and ensures users aren't rate limited too early.

Fixes #

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How should this be tested?

  • Run the new test case in multiple_ratelimits_overcommit_test.go which verifies that monthly counters aren't decremented when minute limits are hit
  • Test with keys that have multiple rate limits (e.g., per-minute and per-month) and verify that hitting one limit doesn't affect the other limit's counter

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

@vercel
Copy link

vercel bot commented Oct 7, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
dashboard Ready Ready Preview Comment Oct 9, 2025 10:54am
engineering Ready Ready Preview Comment Oct 9, 2025 10:54am

@changeset-bot
Copy link

changeset-bot bot commented Oct 7, 2025

⚠️ No Changeset found

Latest commit: ff39a3b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Collaborator Author

chronark commented Oct 7, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

@chronark chronark changed the title test: rejected requests should not modify ratelimit state fix: rejected requests should not modify ratelimit state Oct 7, 2025
@chronark chronark marked this pull request as ready for review October 7, 2025 13:34
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 7, 2025

📝 Walkthrough

Walkthrough

Adds batch rate-limiting support and integrates it into key validation. Introduces RatelimitMany API, refactors service internals (bucket identity, locking, window calculation), and updates validation to call batched checks. Adds tests verifying multi-limit behavior and non-leaky counters when one limit rejects a request.

Changes

Cohort / File(s) Summary
API verify-key tests
go/apps/api/routes/v2_keys_verify_key/multiple_ratelimits_overcommit_test.go, go/apps/api/routes/v2_keys_verify_key/ratelimit_response_test.go
New tests asserting correct multi-limit behavior: per-minute vs. monthly counters, remaining values, exceeded flags, and no counter decrement on rejected requests.
Keys validation batching
go/internal/services/keys/validation.go
Switches from per-limit calls to batched rate limit processing; maps responses back to per-limit results; preserves no-limit early return; writes results to k.RatelimitResults.
Ratelimit interface
go/internal/services/ratelimit/interface.go
Adds RatelimitMany(ctx, []RatelimitRequest) ([]RatelimitResponse, error) to the Service interface; minor doc update.
Ratelimit service core
go/internal/services/ratelimit/service.go
Implements RatelimitMany; refactors locking and bucket evaluation; sliding-window calculation; Redis consult/fallback; replay buffer; config struct; tracing/metrics; single-request path delegates to shared logic.
Ratelimit bucket internals
go/internal/services/ratelimit/bucket.go
Adds bucket identifier, key construction via bucketKey, and populates identifier on creation.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant API as API VerifyKey Route
  participant Keys as Keys Validation
  participant RL as Ratelimit Service

  Client->>API: VerifyKey request
  API->>Keys: Validate(key)
  Keys->>Keys: Collect rate limits to check
  alt 0 limits
    Keys-->>API: Valid (no ratelimits)
  else 1 limit
    Keys->>RL: Ratelimit(req)
    RL->>RL: Evaluate bucket/window
    RL-->>Keys: Response (allowed/exceeded, remaining)
  else >1 limits
    Keys->>RL: RatelimitMany(reqs[])
    RL->>RL: Lock buckets, evaluate all
    RL-->>Keys: Responses[]
  end
  Keys->>Keys: Map responses to per-limit results
  Keys-->>API: Validation result (valid/ratelimited, per-limit states)
  API-->>Client: HTTP response (status, headers)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

Bug, Needs Approval

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description includes the required sections for summary, type of change, testing instructions, and checklist, and it generally follows the repository template. However, the placeholder 'Fixes #' is left blank and does not reference a specific issue number, which is mandatory for tracking and clarity. Without an explicit issue reference, it is unclear which bug or task this change is intended to resolve. Please update the 'Fixes #' line to include the appropriate issue number (for example, 'Fixes #123'), or create and reference a new issue if one does not exist. Ensuring the issue reference is present will improve traceability and clarity for reviewers and maintainers. After adding the issue number, verify that the description matches the template and no placeholders remain.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title concisely summarizes the primary change, indicating a fix to prevent rejected requests from modifying rate limit state and directly reflecting the bug being addressed. It is clear, specific, and avoids extraneous details or vague phrasing. Therefore, it effectively communicates the main intent of the pull request.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 10-07-test_rejected_requests_should_not_modify_ratelimit_state

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a776840 and ff39a3b.

📒 Files selected for processing (5)
  • go/apps/api/routes/v2_keys_verify_key/ratelimit_response_test.go (1 hunks)
  • go/internal/services/keys/validation.go (1 hunks)
  • go/internal/services/ratelimit/bucket.go (3 hunks)
  • go/internal/services/ratelimit/interface.go (1 hunks)
  • go/internal/services/ratelimit/service.go (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • go/internal/services/ratelimit/interface.go
🧰 Additional context used
🧬 Code graph analysis (3)
go/internal/services/keys/validation.go (2)
go/internal/services/ratelimit/interface.go (2)
  • RatelimitRequest (56-97)
  • RatelimitResponse (104-140)
go/internal/services/keys/status.go (1)
  • StatusRateLimited (21-21)
go/apps/api/routes/v2_keys_verify_key/ratelimit_response_test.go (3)
go/pkg/testutil/seed/seed.go (1)
  • CreateRatelimitRequest (307-315)
go/apps/api/openapi/gen.go (3)
  • VerifyKeyRatelimitData (1983-2008)
  • VALID (43-43)
  • RATELIMITED (41-41)
go/pkg/testutil/http.go (1)
  • CallRoute (271-305)
go/internal/services/ratelimit/service.go (5)
go/pkg/clock/interface.go (1)
  • Clock (12-24)
go/pkg/counter/interface.go (1)
  • Counter (21-147)
go/internal/services/ratelimit/interface.go (2)
  • RatelimitRequest (56-97)
  • RatelimitResponse (104-140)
go/pkg/buffer/buffer.go (1)
  • Buffer (14-23)
go/pkg/prometheus/metrics/ratelimit.go (1)
  • RatelimitDecision (112-121)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test API / API Test Local

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel vercel bot temporarily deployed to Preview – dashboard October 7, 2025 13:34 Inactive
@vercel vercel bot temporarily deployed to Preview – engineering October 7, 2025 13:34 Inactive
@coderabbitai coderabbitai bot requested a review from ogzhanolguncu October 7, 2025 13:35
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8379888 and a776840.

📒 Files selected for processing (5)
  • go/apps/api/routes/v2_keys_verify_key/multiple_ratelimits_overcommit_test.go (1 hunks)
  • go/apps/api/routes/v2_ratelimit_limit/handler.go (1 hunks)
  • go/internal/services/keys/validation.go (1 hunks)
  • go/internal/services/ratelimit/interface.go (1 hunks)
  • go/internal/services/ratelimit/service.go (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
go/apps/api/routes/v2_keys_verify_key/multiple_ratelimits_overcommit_test.go (5)
go/pkg/testutil/http.go (2)
  • NewHarness (55-189)
  • CallRoute (271-305)
go/apps/api/routes/v2_ratelimit_limit/handler.go (3)
  • Handler (36-45)
  • Request (32-32)
  • Response (33-33)
go/pkg/testutil/seed/seed.go (1)
  • CreateRatelimitRequest (306-314)
go/apps/api/openapi/gen.go (2)
  • VALID (43-43)
  • RATELIMITED (41-41)
go/pkg/ptr/deref.go (1)
  • SafeDeref (35-44)
go/apps/api/routes/v2_ratelimit_limit/handler.go (1)
go/internal/services/ratelimit/interface.go (1)
  • RatelimitRequest (62-103)
go/internal/services/ratelimit/service.go (5)
go/pkg/clock/interface.go (1)
  • Clock (12-24)
go/pkg/counter/interface.go (1)
  • Counter (21-147)
go/internal/services/ratelimit/interface.go (2)
  • RatelimitRequest (62-103)
  • RatelimitResponse (110-146)
go/pkg/buffer/buffer.go (1)
  • Buffer (14-23)
go/pkg/prometheus/metrics/ratelimit.go (1)
  • RatelimitDecision (112-121)
go/internal/services/ratelimit/interface.go (1)
go/pkg/db/models_generated.go (1)
  • Ratelimit (736-747)
go/internal/services/keys/validation.go (2)
go/internal/services/ratelimit/interface.go (1)
  • RatelimitRequest (62-103)
go/internal/services/keys/status.go (1)
  • StatusRateLimited (21-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Build / Build

Flo4604 and others added 5 commits October 8, 2025 19:30
@github-actions
Copy link
Contributor

github-actions bot commented Oct 9, 2025

Thank you for following the naming conventions for pull request titles! 🙏

@vercel vercel bot temporarily deployed to Preview – dashboard October 9, 2025 10:54 Inactive
@vercel vercel bot temporarily deployed to Preview – engineering October 9, 2025 10:54 Inactive
@chronark chronark merged commit f59b5c6 into main Oct 9, 2025
17 checks passed
@chronark chronark deleted the 10-07-test_rejected_requests_should_not_modify_ratelimit_state branch October 9, 2025 11:12
@coderabbitai coderabbitai bot mentioned this pull request Nov 4, 2025
19 tasks
@coderabbitai coderabbitai bot mentioned this pull request Nov 12, 2025
19 tasks
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.

2 participants