Skip to content

feat: add rate limiting on auth and API endpoints#117

Merged
ryota-murakami merged 3 commits intomainfrom
feat/issue-77-rate-limiting
Feb 13, 2026
Merged

feat: add rate limiting on auth and API endpoints#117
ryota-murakami merged 3 commits intomainfrom
feat/issue-77-rate-limiting

Conversation

@ryota-murakami
Copy link
Contributor

@ryota-murakami ryota-murakami commented Feb 13, 2026

Summary

Closes #77

Add in-memory sliding-window rate limiting to protect against brute-force enumeration of board IDs, excessive GitHub API consumption, and denial of service via expensive queries.

Zero new dependencies — pure TypeScript in-memory implementation with identical interface to @upstash/ratelimit for easy future swap if needed.

Rate Limits Applied

Endpoint Limit Identifier Priority
/auth/callback (proxy.ts) 10 req/min IP Critical
signInWithGitHub() 10 req/hour IP Critical
deleteAccount() 3 req/hour user ID Critical
GitHub API (4 functions) 30 req/min IP High
addRepositoriesToBoard() 10 req/min user ID High
DnD batch ops (3 functions) 60 req/min user ID Medium
Board CRUD (create/delete + positions) 20 req/min user ID Medium

Architecture

  • src/lib/rate-limit/config.ts — Centralized rate limit definitions
  • src/lib/rate-limit/memory.tsSlidingWindowLimiter class (Map-based sliding window with periodic cleanup)
  • src/lib/rate-limit/check.ts — Main entry point with test-mode bypass and Sentry logging
  • src/lib/rate-limit/edge-memory.ts — Edge-runtime compatible fixed-window limiter for proxy.ts
  • src/lib/actions/auth-guard.ts — New withAuthRateLimit and withAuthResultRateLimit wrappers

Error Handling

Context User Experience
OAuth callback HTTP 429 + Retry-After: 60
Sign-in Redirect to /login?error=rate_limited
Server actions (ActionResult) { success: false, error: "Too many X requests..." } → toast
Delete account Error thrown → caught by UI error handler

Design Decisions

  • In-memory over Redis: Zero cost, zero infrastructure. Per-instance state is acceptable for GitBox's single-region deployment (cold starts are lenient, not restrictive)
  • Write-only scope: Read-only actions protected by Supabase RLS, no rate limiting needed
  • Test bypass: isTestMode() auto-bypasses all rate limiting in E2E/unit tests

Test plan

  • pnpm typecheck — passes
  • pnpm lint — passes (0 warnings)
  • pnpm test — 1282 tests pass (includes new SlidingWindowLimiter unit tests)
  • pnpm build — successful
  • pnpm e2e — rate limiting correctly bypassed in test mode (276 passed, 46 pre-existing flaky)

Summary by CodeRabbit

  • New Features
    • Global rate limiting applied to sign-in, OAuth callback, GitHub integrations, board/repo actions, and account deletion — users hitting limits will receive rate-limit errors or be redirected.
  • Bug Fixes / Behavior
    • Mutating operations (boards, batch moves, repo adds, deletions) are now throttled to improve stability under high concurrency.
  • Tests
    • Added in-memory rate limiter tests validating sliding-window behavior and expiry.

Add sliding-window rate limiting to protect against brute-force and abuse:
- OAuth callback: 10 req/min per IP (proxy.ts edge)
- signInWithGitHub: 10 req/hour per IP
- deleteAccount: 3 req/hour per user
- GitHub API proxy: 30 req/min per IP
- addRepositoriesToBoard: 10 req/min per user
- DnD batch operations: 60 req/min per user
- Board CRUD: 20 req/min per user

Zero new dependencies — in-memory implementation with same
interface as Upstash for easy future swap.
@vercel
Copy link
Contributor

vercel bot commented Feb 13, 2026

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

Project Deployment Actions Updated (UTC)
gitbox Ready Ready Preview, Comment Feb 13, 2026 2:49pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Adds a configurable rate-limiting system and integrates it into auth, server actions, GitHub proxy, and the OAuth middleware; includes in-memory sliding-window and edge-compatible limiters, per-endpoint configs, logging of rate_limited security events, tests, and new auth-wrapper variants enforcing rate limits.

Changes

Cohort / File(s) Summary
Rate limit core
src/lib/rate-limit/config.ts, src/lib/rate-limit/check.ts
New RATE_LIMIT_CONFIG and RateLimitKey type; checkRateLimit(key, identifier) added returning structured RateLimitResult and lazily-instantiates per-key limiters; bypasses checks in test mode and warns on empty identifiers.
In-memory limiters
src/lib/rate-limit/memory.ts, src/lib/rate-limit/edge-memory.ts, src/lib/rate-limit/__tests__/memory.test.ts
Added SlidingWindowLimiter (memory) with cleanup and destroy(); edge-compatible fixed-window edgeRateLimit and getClientIp; comprehensive sliding-window tests using fake timers.
Auth guard wrappers
src/lib/actions/auth-guard.ts
Added withAuthResultRateLimit and withAuthRateLimit that authenticate, call checkRateLimit (user.id), and either return structured ActionResult errors or throw on failure; preserves Sentry logging.
Auth actions
src/lib/actions/auth.ts
Applied IP-based rate limit for signInWithGitHub (uses stored headers/getClientIp), and user-id rate limit for deleteAccount; redirects or throws on limit as appropriate.
Board actions
src/lib/actions/board.ts
Switched mutating and batch board operations to use rate-limited wrappers (batchDnD, boardCrud) instead of prior non-rate-limited auth wrappers.
GitHub proxy/actions
src/lib/actions/github.ts
Added getClientIp() usage and pre-call checkRateLimit('githubApi', ip) checks for repository/user/org endpoints with early return on limits.
Repo card actions
src/lib/actions/repo-cards.ts
Added checkRateLimit('addReposToBoard', user.id) to addRepositoriesToBoard, returning a failure with addedCount: 0 on limit.
OAuth middleware
src/proxy.ts
Added IP-based edgeRateLimit for /auth/callback using RATE_LIMIT_CONFIG; returns 429 with Retry-After and logs rate_limited security event.
Security events
src/lib/security-events.ts
Added rate_limited to SecurityEventType and mapped it to warning log level.
Package manifest
package.json
Updated manifest (file listed in diff).

Sequence Diagram

sequenceDiagram
    actor Client
    participant Proxy as Proxy/Middleware
    participant RateLimitEdge as Edge Rate Limiter
    participant Guard as Auth Guard
    participant RateLimitServer as Server Rate Limiter
    participant Action as Server Action
    participant Security as Security Events

    Client->>Proxy: HTTP request (e.g., /auth/callback or action)
    Proxy->>RateLimitEdge: edgeRateLimit(key, clientIp)
    alt Edge limit exceeded
        RateLimitEdge->>Security: log rate_limited
        Security-->>Proxy: logged
        Proxy-->>Client: 429 + Retry-After
    else Edge allowed
        Proxy->>Guard: withAuthRateLimit/withAuthResultRateLimit
        Guard->>RateLimitServer: checkRateLimit(key, userId or ip)
        alt Server limit exceeded
            RateLimitServer->>Security: log rate_limited
            Security-->>Guard: logged
            Guard-->>Client: ActionResult error / throw
        else Server allowed
            Guard->>Action: execute protected action
            Action-->>Guard: result
            Guard-->>Client: success response
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🚦 Limits placed where traffic once flew free,
A sliding window guards each fragile key,
IPs and users kept in careful view,
Events whisper "rate_limited"—warning true,
Small sentries stand so systems can breathe.

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding rate limiting to auth and API endpoints. It is concise, specific, and reflects the core objective of the PR.
Linked Issues check ✅ Passed The PR fully addresses issue #77 requirements: implements rate limiting on OAuth login (/auth/callback), server actions (signInWithGitHub, deleteAccount, board CRUD), and GitHub API proxy with a custom in-memory solution.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing rate limiting. No unrelated refactoring, dependencies, or functionality changes beyond the scope of issue #77 are present.
Docstring Coverage ✅ Passed Docstring coverage is 95.65% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/issue-77-rate-limiting

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.

@morph-subagents
Copy link

🤖 Morph Preview Test

Looks like you hit your rate limits!

Please upgrade your limits here, or wait a few minutes and try again.

If you need help, reach out to support@morphllm.com.


Automated testing by Morph

@codecov-commenter
Copy link

codecov-commenter commented Feb 13, 2026

Codecov Report

❌ Patch coverage is 5.12821% with 74 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.51%. Comparing base (a9d0653) to head (30f6c0f).

Files with missing lines Patch % Lines
src/lib/rate-limit/memory.ts 0.00% 34 Missing ⚠️
src/lib/rate-limit/check.ts 8.69% 21 Missing ⚠️
src/lib/rate-limit/edge-memory.ts 5.88% 16 Missing ⚠️
src/lib/security-events.ts 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #117      +/-   ##
==========================================
- Coverage   73.74%   72.51%   -1.24%     
==========================================
  Files         138      142       +4     
  Lines        4125     4202      +77     
  Branches     1078     1128      +50     
==========================================
+ Hits         3042     3047       +5     
- Misses       1062     1134      +72     
  Partials       21       21              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@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: 2

🤖 Fix all issues with AI agents
In `@src/lib/actions/auth.ts`:
- Around line 30-39: The fallback IP "127.0.0.1" used in the sign-in flow can
cause a shared rate-limit bucket when x-forwarded-for is missing; update the
code around headers()/headerStore and the ip variable in the signInWithGitHub
rate-limiting block to detect when x-forwarded-for is absent and emit a
warning/telemetry (e.g., processLogger.warn or Sentry.captureMessage) with
context (headers or request ID) before using a safer per-request fallback (such
as generating a short random token or including another header like
cf-connecting-ip) and then pass that value into
checkRateLimit('signInWithGitHub', ip); ensure the added logging/telemetry call
is non-blocking and does not expose sensitive header contents.

In `@src/lib/rate-limit/check.ts`:
- Around line 76-93: The rate-limit handler currently emits two Sentry events
because logSecurityEvent('rate_limited', ...) already calls
Sentry.captureMessage; remove the explicit Sentry.captureMessage(...) block in
checkRateLimit (the code that builds `Rate limit hit: ${key}` with tags {
category: 'rate_limit', action: key }) so only logSecurityEvent is used; if you
need the extra Sentry tags/metadata, extend logSecurityEvent (or its call site)
to accept and forward an extra tags/extra payload (e.g., include action/key and
identifier) rather than duplicating a direct Sentry.captureMessage call.
🧹 Nitpick comments (5)
src/lib/rate-limit/check.ts (2)

79-82: userId field populated with IP addresses is misleading.

For IP-based rate limiting (e.g., githubApi, auth/callback), identifier is an IP address, but it's logged as userId in the security event context. This will produce confusing audit trails in Sentry where IP addresses appear in the userId field.

Consider using the ip field from SecurityEventContext when the identifier is an IP:

Suggested approach
     logSecurityEvent('rate_limited', {
-      userId: identifier,
+      ip: identifier,
       path: key,
     })

If you need to distinguish between user-ID-keyed and IP-keyed limiters, you could pass both fields or add a discriminator.


23-28: RateLimitResult could benefit from a discriminated union for type safety.

Currently callers use rlResult.error! (non-null assertion) after checking !rlResult.allowed. A discriminated union would eliminate the need for !:

export type RateLimitResult =
  | { allowed: true }
  | { allowed: false; error: string }

This is a minor ergonomic improvement — not blocking.

src/lib/actions/github.ts (1)

24-33: Separate getClientIp for server actions is reasonable but duplicates logic.

The edge-runtime version in edge-memory.ts takes a Request parameter, while this one uses Next.js headers() — different contexts justify separate implementations. That said, the IP-parsing logic (x-forwarded-for split + trim + fallback) is duplicated. If you ever need to change the parsing (e.g., add x-real-ip fallback like edge-memory.ts does), you'll need to update both.

Consider extracting the parsing into a shared utility, or at minimum, note the edge version also checks x-real-ip while this one does not — a minor inconsistency.

src/lib/rate-limit/edge-memory.ts (1)

14-46: No eviction for stale entries — acceptable for edge, but worth a passive cleanup.

Expired entries are lazily overwritten when the same identifier returns (line 35-37), but IPs that visit once and never return will linger. In a short-lived edge worker this is a non-issue. If the function stays warm for extended periods, consider opportunistic eviction — e.g., delete a few expired entries when store.size exceeds a threshold inside edgeRateLimit itself (no setInterval needed).

src/lib/actions/auth-guard.ts (1)

135-168: Optional: reduce auth boilerplate duplication via composition.

withAuthResultRateLimit and withAuthRateLimit each duplicate the 8-line auth block from their non-rate-limited counterparts. You could compose them:

♻️ Sketch (optional)
 export async function withAuthResultRateLimit<T>(
   rateLimitKey: RateLimitKey,
   action: (supabase: SupabaseClient<Database>, user: User) => Promise<T>,
 ): Promise<ActionResult<T>> {
-  const supabase = await createClient()
-  const {
-    data: { user },
-    error: authError,
-  } = await supabase.auth.getUser()
-
-  if (authError || !user) {
-    return { success: false, error: 'Authentication required' }
-  }
-
-  // Rate limit check using user.id
-  const rlResult = checkRateLimit(rateLimitKey, user.id)
-  if (!rlResult.allowed) {
-    return { success: false, error: rlResult.error! }
-  }
-
-  try {
-    const data = await action(supabase, user)
-    return { success: true, data }
-  } catch (error) {
-    Sentry.captureException(error, {
-      extra: { context: `withAuthResultRateLimit:${rateLimitKey}` },
-    })
-    return {
-      success: false,
-      error:
-        error instanceof Error ? error.message : 'An unexpected error occurred',
-    }
-  }
+  return withAuthResult(async (supabase, user) => {
+    const rlResult = checkRateLimit(rateLimitKey, user.id)
+    if (!rlResult.allowed) {
+      throw new Error(rlResult.error!)
+    }
+    return action(supabase, user)
+  })
 }

This keeps the rate-limit error surfacing through the existing withAuthResult catch block. Up to you whether the indirection is worth the dedup.

@github-actions
Copy link

github-actions bot commented Feb 13, 2026

🧪 E2E Coverage Report (Sharded: 12 parallel jobs)

Metric Coverage
Lines 94.38%
Functions 17.7%
Branches 16.88%
Statements 30.48%

📊 Full report available in workflow artifacts

- Remove duplicate Sentry.captureMessage in check.ts (logSecurityEvent
  already emits to Sentry internally)
- Add warning log when x-forwarded-for header is missing in auth.ts and
  github.ts to detect proxy misconfiguration in production
@morph-subagents
Copy link

🤖 Morph Preview Test

Looks like you hit your rate limits!

Please upgrade your limits here, or wait a few minutes and try again.

If you need help, reach out to support@morphllm.com.


Automated testing by Morph

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/laststance/gitbox/issues/comments/3897398488","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Walkthrough\n\nAdds a configurable rate-limiting system and integrates it into auth flows, server actions, GitHub proxy calls, and the OAuth middleware; includes in-memory sliding-window and edge-compatible limiters, security-event logging for rate limits, tests, and new auth-guard wrappers enforcing rate limits.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**Rate limit core** <br> `src/lib/rate-limit/config.ts`, `src/lib/rate-limit/check.ts`|New RATE_LIMIT_CONFIG, RateLimitKey type, and `checkRateLimit` API that returns `{ allowed, error? }` and logs security events on violations.|\n|**In-memory limiters & utils** <br> `src/lib/rate-limit/memory.ts`, `src/lib/rate-limit/edge-memory.ts`|Added SlidingWindowLimiter (memory) with cleanup and destroy(), and an edge-compatible fixed-window `edgeRateLimit` plus `getClientIp`.|\n|**Tests** <br> `src/lib/rate-limit/__tests__/memory.test.ts`|Unit tests for SlidingWindowLimiter covering limits, expiry, cleanup, and independent identifiers using fake timers.|\n|**Security events** <br> `src/lib/security-events.ts`|Added `rate_limited` event type and map it to warning log level.|\n|**Auth guard wrappers** <br> `src/lib/actions/auth-guard.ts`|Added `withAuthResultRateLimit` and `withAuthRateLimit` that authenticate, call `checkRateLimit` (user ID), and either return structured errors or throw on rate-limit/auth failures; preserves Sentry reporting.|\n|**Auth actions** <br> `src/lib/actions/auth.ts`|Integrated IP-based rate limiting for `signInWithGitHub` (redirects with rate_limited error) and user-ID rate limit for `deleteAccount` (throws on limit); uses headerStore for origin.|\n|**Board actions** <br> `src/lib/actions/board.ts`|Switched batch and CRUD mutation wrappers to rate-limited variants (`withAuthRateLimit` / `withAuthResultRateLimit`) with keys like `batchDnD` and `boardCrud`.|\n|**GitHub proxy/actions** <br> `src/lib/actions/github.ts`|Added `getClientIp()` and IP-based `checkRateLimit` before GitHub API calls; early-return error on limit.|\n|**Repo-card actions** <br> `src/lib/actions/repo-cards.ts`|Added rate-limit check in `addRepositoriesToBoard`; returns zero-count with error when limited.|\n|**OAuth middleware** <br> `src/proxy.ts`|Applied `edgeRateLimit` to `/auth/callback` (IP-based), logs security event and returns 429 with Retry-After when limited.|\n|**Exports / imports** <br> various files|Updated imports/exports to use new auth-guard rate-limited wrappers and added rate-limit-related imports across actions and proxy.|\n\n## Sequence Diagram\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Proxy as Proxy / Middleware\n    participant RateLimit as Rate Limiter\n    participant Guard as Auth Guard\n    participant Action as Server Action\n    participant Security as Security Events\n\n    Client->>Proxy: HTTP request (e.g., /auth/callback or action)\n    Proxy->>RateLimit: check (key, identifier IP/user)\n    alt limited\n        RateLimit->>Security: log rate_limited (key, identifier)\n        Security-->>Proxy: logged\n        Proxy-->>Client: 429 / Error (Retry-After or structured error)\n    else allowed\n        RateLimit-->>Proxy: allowed\n        Proxy->>Guard: withAuthRateLimit / withAuthResultRateLimit\n        Guard->>RateLimit: check (key, user.id)\n        alt limited\n            RateLimit->>Security: log rate_limited (user.id)\n            Security-->>Guard: logged\n            Guard-->>Client: ActionResult error / throw\n        else allowed\n            Guard->>Action: execute action (DB / GitHub call)\n            Action-->>Guard: result\n            Guard-->>Client: success response\n        end\n    end\n```\n\n## Estimated code review effort\n\n🎯 4 (Complex) | ⏱️ ~60 minutes\n\n## Possibly related PRs\n\n- laststance/gitbox#89 — Earlier auth-guard work that introduced base authentication wrappers; this PR extends those wrappers with rate-limited variants.\n\n## Suggested labels\n\n`security`\n\n## Poem\n\n> 🚦 Limits rise where traffic churns, polite and tight,  \n> Sliding windows hum to keep the bursts in sight.  \n> Guards check the keys, the IPs, the user stride,  \n> Alerts whisper warnings when the floods collide.  \n> Small sentries, big calm — the APIs sleep tight.\n\n</details>\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 6</summary>\n\n<details>\n<summary>✅ Passed checks (6 passed)</summary>\n\n|         Check name         | Status   | Explanation                                                                                                                                                                                                                     |\n| :------------------------: | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n|      Description Check     | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled.                                                                                                                                                                     |\n|         Title check        | ✅ Passed | The title accurately and concisely summarizes the main change: adding rate limiting to authentication and API endpoints across the codebase.                                                                                    |\n|     Linked Issues check    | ✅ Passed | The PR fully addresses issue `#77` requirements: implements rate limiting on OAuth login (`#77`), server actions (`#77`), and GitHub API proxy (`#77`) using a custom in-memory sliding-window limiter as suggested.                    |\n| Out of Scope Changes check | ✅ Passed | All changes directly support rate limiting implementation: core limiter logic (memory.ts, edge-memory.ts, check.ts, config.ts), auth/action wrappers, endpoint integration, security event logging, and tests are all in scope. |\n|     Docstring Coverage     | ✅ Passed | Docstring coverage is 95.65% which is sufficient. The required threshold is 80.00%.                                                                                                                                             |\n|  Merge Conflict Detection  | ✅ Passed | ✅ No merge conflicts detected when merging into `main`                                                                                                                                                                          |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `feat/issue-77-rate-limiting`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=laststance/gitbox&utm_content=117)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"retries":3,"retryAfter":16}}}

Copy link

@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

🤖 Fix all issues with AI agents
In `@src/lib/rate-limit/check.ts`:
- Around line 62-69: In checkRateLimit, add a guard for a missing/empty
identifier (the identifier parameter) so we don't use an empty string as a
shared rate-limit bucket; if identifier is falsy/empty, return a RateLimitResult
that denies the request (e.g., { allowed: false, reason: 'missing identifier' })
and log or record a warning before doing any bucket lookups; update any tests or
call sites that expect different behavior accordingly.
🧹 Nitpick comments (1)
src/lib/rate-limit/check.ts (1)

77-80: userId field holds an IP for unauthenticated endpoints — consider clarity.

When identifier is a client IP (e.g., signInWithGitHub, auth/callback), logging it as userId in Sentry could confuse triage. A field like identifier or splitting into userId / ip would make dashboards clearer. Low priority — just a nit.

Prevent empty string identifiers from creating a shared rate-limit
bucket. Log a warning via module logger when this edge case occurs.
@morph-subagents
Copy link

🤖 Morph Preview Test

Looks like you hit your rate limits!

Please upgrade your limits here, or wait a few minutes and try again.

If you need help, reach out to support@morphllm.com.


Automated testing by Morph

Copy link

@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

🤖 Fix all issues with AI agents
In `@src/lib/rate-limit/check.ts`:
- Around line 69-73: The guard that bypasses rate-limiting when identifier is
empty currently only calls log.warn and returns { allowed: true }; add a Sentry
security event before returning by invoking the existing logSecurityEvent
function (e.g., logSecurityEvent('empty_rate_limit_identifier', { key })) so the
empty-identifier bypass is recorded like other rate-limit events; place this
call in the same block where identifier and key are available (near the existing
log.warn) prior to returning.
🧹 Nitpick comments (1)
src/lib/rate-limit/check.ts (1)

1-97: Clean implementation with good docs and past issues addressed.

The duplicate Sentry call is removed, and the empty-identifier guard is in place. JSDoc coverage is solid.

One minor note: on line 87, the field userId receives the identifier param, which may be an IP address for unauthenticated endpoints (e.g., signInWithGitHub). This can be misleading when reviewing security events. Consider renaming to a neutral field like subject or identifier if the SecurityEventContext type allows it.

@ryota-murakami ryota-murakami merged commit f1e2fc0 into main Feb 13, 2026
20 checks passed
@ryota-murakami ryota-murakami deleted the feat/issue-77-rate-limiting branch February 13, 2026 15:00
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.

Add rate limiting on auth and API endpoints

2 participants