🛡️ Sentinel: implement dual rate-limiting on authentication server actions - #122
🛡️ Sentinel: implement dual rate-limiting on authentication server actions#122projectamazonph wants to merge 3 commits into
Conversation
…tions Co-authored-by: projectamazonph <286085559+projectamazonph@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
|
Warning Review limit reached
Next review available in: 49 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. 📝 WalkthroughWalkthroughAuthentication signup and signin now apply IP-first dual rate limiting. The shared limiter reads request headers, supports target-only fallback, and includes reset support. Tests cover enforcement, expiration, fallback, and cleanup behavior. ChangesAuthentication rate limiting
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant AuthActions
participant rateLimitDual
participant NextHeaders
participant RateLimitBuckets
AuthActions->>rateLimitDual: Provide IP prefix and email target
rateLimitDual->>NextHeaders: Read client IP
rateLimitDual->>RateLimitBuckets: Check IP limit
rateLimitDual->>RateLimitBuckets: Check email target limit if IP is allowed
rateLimitDual-->>AuthActions: Return allow or first denial
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
.jules/sentinel.md (1)
8-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDefine the security terms for the intended audience.
Define “credential stuffing” and “target bucket” when first used. Use plain terms such as “many sign-in attempts against many accounts” and “the counter for one email address.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.jules/sentinel.md around lines 8 - 11, Update the “Dual Rate-Limiting Ordering Prevents Account Lockout DoS” entry in .jules/sentinel.md to define “credential stuffing” on first use as many sign-in attempts against many accounts, and define “target bucket” as the counter for one email address. Keep the existing security guidance and ordering requirements unchanged.Source: Coding guidelines
src/app/actions/auth.ts (1)
35-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRevise the new comments.
Line 35 restates the
rateLimitDualcall. Remove it or explain the reason for the ordering. Replace the em dash on Line 133 with a period or comma.Also applies to: 132-134
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/actions/auth.ts` at line 35, Revise the comments around the rateLimitDual call by removing the comment that merely restates the call, or explain why the client-IP and target-based checks are ordered as shown; also replace the em dash in the nearby comment with a period or comma.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/actions/auth.ts`:
- Around line 35-41: The action-level regression tests should cover both
limiter-denial paths in src/app/actions/auth.ts at lines 35-41 and 132-140: add
focused sign-up and sign-in tests that mock a denied rateLimitDual result,
assert the action returns the expected denial response, and verify sign-up does
not perform user lookup or creation while sign-in does not perform user lookup
or password verification.
In `@src/lib/__tests__/rate-limit.test.ts`:
- Line 68: Replace the `any` casts on the `headers` mocks in the rate-limit
tests with `vi.mocked(headers)`, using a typed header factory for resolved mocks
and a typed rejected mock for failure cases. Update all affected occurrences
while preserving their existing mock behavior.
In `@src/lib/rate-limit.ts`:
- Around line 89-96: Update rateLimitDual and its underlying rateLimit state
management to use shared, atomic storage instead of the per-instance in-memory
Map, using the project’s supported distributed limiter such as Upstash Ratelimit
or Vercel KV. Preserve the existing IP-first and target-key checks, limits,
windows, and return behavior while ensuring state is shared across replicas and
restarts.
---
Nitpick comments:
In @.jules/sentinel.md:
- Around line 8-11: Update the “Dual Rate-Limiting Ordering Prevents Account
Lockout DoS” entry in .jules/sentinel.md to define “credential stuffing” on
first use as many sign-in attempts against many accounts, and define “target
bucket” as the counter for one email address. Keep the existing security
guidance and ordering requirements unchanged.
In `@src/app/actions/auth.ts`:
- Line 35: Revise the comments around the rateLimitDual call by removing the
comment that merely restates the call, or explain why the client-IP and
target-based checks are ordered as shown; also replace the em dash in the nearby
comment with a period or comma.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 92ed1e7d-501a-4036-88b9-22b71b5290b3
📒 Files selected for processing (4)
.jules/sentinel.mdsrc/app/actions/auth.tssrc/lib/__tests__/rate-limit.test.tssrc/lib/rate-limit.ts
| // Use dual rate-limiting combining client IP check and target-based checks (email) | ||
| const rl = await rateLimitDual('signup-ip', `signup:${data.email.toLowerCase()}`, { | ||
| ipLimit: 10, | ||
| ipWindowMs: 60_000, | ||
| targetLimit: 5, | ||
| targetWindowMs: 60_000, | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Add action-level regression tests for the dual limiter.
The utility tests do not prove that both authentication actions enforce limits before database and password work. Add focused tests for denied requests and confirm that each action does not call its database or password-processing dependency after the limiter denies the request.
src/app/actions/auth.ts#L35-L41: add a sign-up action test for IP or email limit denial before user lookup or creation.src/app/actions/auth.ts#L132-L140: add a sign-in action test for IP or email limit denial before user lookup and password verification.
📍 Affects 1 file
src/app/actions/auth.ts#L35-L41(this comment)src/app/actions/auth.ts#L132-L140
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/actions/auth.ts` around lines 35 - 41, The action-level regression
tests should cover both limiter-denial paths in src/app/actions/auth.ts at lines
35-41 and 132-140: add focused sign-up and sign-in tests that mock a denied
rateLimitDual result, assert the action returns the expected denial response,
and verify sign-up does not perform user lookup or creation while sign-in does
not perform user lookup or password verification.
Source: Coding guidelines
| describe('rateLimitDual', () => { | ||
| it('applies IP rate limit before target-based check', async () => { | ||
| // Mock headers with IP 1.2.3.4 | ||
| (headers as any).mockResolvedValue({ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Confirm all unsafe casts are removed from this test file.
rg -n -C 2 '\bheaders\s+as\s+any\b|\bas\s+any\b' src/lib/__tests__/rate-limit.test.tsRepository: projectamazonph/amph-v2
Length of output: 1382
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Relevant test file sections:"
sed -n '1,220p' src/lib/__tests__/rate-limit.test.ts
echo
echo "Find exported mock helpers and headers type usage:"
rg -n "headers\(|mockResolvedValue|mockRejectedValue|vi\.mocked|as any|type Headers|interface Headers" src/lib __tests__ . --glob '!node_modules' --glob '!dist' --glob '!build' | head -200Repository: projectamazonph/amph-v2
Length of output: 29530
Remove any from the headers mocks.
src/lib/__tests__/rate-limit.test.ts still casts headers to any at lines 68, 101, 162, 178, and 193. Use vi.mocked(headers) with a typed header factory or typed rejected mock instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/__tests__/rate-limit.test.ts` at line 68, Replace the `any` casts on
the `headers` mocks in the rate-limit tests with `vi.mocked(headers)`, using a
typed header factory for resolved mocks and a typed rejected mock for failure
cases. Update all affected occurrences while preserving their existing mock
behavior.
Source: Coding guidelines
| const ipResult = rateLimit(ipKey, ipLimit, ipWindowMs); | ||
| if (!ipResult.allowed) { | ||
| return ipResult; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return rateLimit(targetKey, targetLimit, targetWindowMs); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Identify current deployment and shared-cache capabilities before selecting a limiter backend.
fd -HI -t f '^(package\.json|Dockerfile.*|docker-compose.*|vercel\.json|.*\.ya?ml)$' . \
-x sh -c 'echo "== $1 =="; rg -n -i -C 2 "redis|upstash|rate.limit|replica|serverless|scale" "$1" || true' sh {}Repository: projectamazonph/amph-v2
Length of output: 1049
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)(package\.json|packages\.json|pnpm-workspace\.yaml|.*github/workflows/deploy.*\.ya?ml|app\.json|next\.config\.(js|mjs|ts)|vercel\.json|package\.json|Dockerfile.*|docker-compose.*|.*\.ya?ml)$' || true
echo
echo "== rate-limit outline =="
ast-grep outline src/lib/rate-limit.ts --view expanded || true
echo
echo "== rate-limit relevant content =="
sed -n '1,140p' src/lib/rate-limit.ts
echo
echo "== usages of rateLimitDual/resetRateLimits =="
rg -n "rateLimitDual|resetRateLimits|from ['\"].*rate-limit" .Repository: projectamazonph/amph-v2
Length of output: 7400
Store authentication rate-limit state in shared storage.
rateLimitDual uses an in-memory Map per server instance, so each replica and restart gets fresh limits. Multi-instance deployments do not get distributed blocking; use a shared atomic window limiter such as Upstash Ratelimit or Vercel KV.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/rate-limit.ts` around lines 89 - 96, Update rateLimitDual and its
underlying rateLimit state management to use shared, atomic storage instead of
the per-instance in-memory Map, using the project’s supported distributed
limiter such as Upstash Ratelimit or Vercel KV. Preserve the existing IP-first
and target-key checks, limits, windows, and return behavior while ensuring state
is shared across replicas and restarts.
…tions and update pnpm to a healthy version Co-authored-by: projectamazonph <286085559+projectamazonph@users.noreply.github.com>
…tions and update pnpm to a healthy version Co-authored-by: projectamazonph <286085559+projectamazonph@users.noreply.github.com>
🚨 Severity: HIGH
💡 Vulnerability: Legacy target-based rate-limiting only limited signup and signin attempts by the lowercase email address. This was vulnerable to distributed brute force and credential stuffing attacks (where an attacker tries different email addresses from the same client IP). Additionally, without dual rate-limiting, malicious IP-blocked actors could pollute target-based buckets, triggering lockout or DoS for legitimate users.
🎯 Impact: Malicious clients could bypass authentication brute-force protections by trying distinct emails, or cause Account Lockout DoS for target users.
🔧 Fix:
rateLimitDualfunction insrc/lib/rate-limit.tsthat enforces client IP limits extracted safely fromx-forwarded-for(with safe split array bounds checking) orx-real-ipbefore verifying target-based buckets (emails).rateLimitDualin bothsignUpActionandsignInActionserver actions.resetRateLimitshelper to clean up rate limiter maps across test suites.src/lib/__tests__/rate-limit.test.ts.✅ Verification: All 221 vitest tests pass perfectly, including thorough unit tests for the sliding window, dual limits, fallback header handling, and target pollution avoidance on IP failure.
PR created automatically by Jules for task 6325790428740304422 started by @projectamazonph
Summary by CodeRabbit
Security
Testing