Skip to content

🛡️ Sentinel: dual rate-limiting security enhancement - #104

Open
projectamazonph wants to merge 2 commits into
mainfrom
fix/dual-rate-limiting-360127238735165060
Open

🛡️ Sentinel: dual rate-limiting security enhancement#104
projectamazonph wants to merge 2 commits into
mainfrom
fix/dual-rate-limiting-360127238735165060

Conversation

@projectamazonph

@projectamazonph projectamazonph commented Aug 3, 2026

Copy link
Copy Markdown
Owner

This security enhancement implements dual rate-limiting (checking both client IP and target-based identifiers like emails) on signup and signin server actions. It resolves potential credential-stuffing and brute-force vulnerabilities, while ensuring IP rate limiting is executed first to prevent target-based bucket pollution and account lockout Denial of Service (DoS).


PR created automatically by Jules for task 360127238735165060 started by @projectamazonph

Summary by CodeRabbit

  • Security

    • Improved sign-up and sign-in protection with separate limits for individual accounts and client connections.
    • Helps prevent excessive attempts from one connection from unfairly blocking access for other accounts.
    • Added safer handling when client connection details are unavailable.
  • Tests

    • Expanded coverage for authentication rate limiting, request identification, time-window expiration, and blocked attempts.

Co-authored-by: projectamazonph <286085559+projectamazonph@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 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 @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings August 3, 2026 12:47
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@projectamazonph, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 54 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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5dc10178-8e60-4b29-b634-6059e79a41d7

📥 Commits

Reviewing files that changed from the base of the PR and between b401813 and 3ebf019.

📒 Files selected for processing (1)
  • package.json
📝 Walkthrough

Walkthrough

Authentication signup and signin now use IP-first dual rate limiting. The limiter reads client IP headers, falls back to unknown, and applies separate IP and target limits. Tests cover limiter behavior, header parsing, and authentication mocks.

Changes

Authentication rate limiting

Layer / File(s) Summary
Dual rate limiter implementation and validation
src/lib/rate-limit.ts, src/lib/__tests__/rate-limit.test.ts, src/__tests__/setup.ts, .jules/sentinel.md
Added rateLimitDual with IP-first enforcement, header fallbacks, bucket reset support, and coverage for expiration, denial, and bucket isolation.
Authentication action integration
src/app/actions/auth.ts, src/app/actions/__tests__/auth-actions.test.ts
Signup and signin now apply 5 target attempts and 10 IP attempts per 60-second window. Async header mocks support the updated actions.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AuthAction
  participant rateLimitDual
  participant Headers
  participant RateLimitBuckets
  AuthAction->>rateLimitDual: Submit target and limit options
  rateLimitDual->>Headers: Read client IP headers
  Headers-->>rateLimitDual: Return IP or unknown
  rateLimitDual->>RateLimitBuckets: Check IP bucket
  RateLimitBuckets-->>rateLimitDual: Allow or deny
  rateLimitDual->>RateLimitBuckets: Check target bucket if allowed
  RateLimitBuckets-->>rateLimitDual: Allow or deny
  rateLimitDual-->>AuthAction: Return RateLimitResult
Loading

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the dual rate-limiting security enhancement implemented for signup and signin.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dual-rate-limiting-360127238735165060

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 resolve CI pnpm version issue

Co-authored-by: projectamazonph <286085559+projectamazonph@users.noreply.github.com>

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/__tests__/rate-limit.test.ts (1)

60-146: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace (headers as any) casts with vi.mocked(headers).

This file casts headers to any before calling .mockResolvedValue/.mockRejectedValue at lines 60, 78, 89, 101, 112, 128, and 141. The coding guidelines require strict TypeScript and forbid any. Since headers is already vi.fn(), use vi.mocked(headers) to keep full typing without the cast.

As per coding guidelines, "Use strict TypeScript. Do not use any; define explicit types or use unknown with narrowing."

🔧 Proposed fix
-      (headers as any).mockResolvedValue({
+      vi.mocked(headers).mockResolvedValue({
         get: (name: string) => {
           if (name === 'x-forwarded-for') return '1.2.3.4';
           return null;
         },
       });

Apply the same replacement at each (headers as any) occurrence in this file.

🤖 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` around lines 60 - 146, Replace every
`(headers as any)` cast in the rate-limit tests with `vi.mocked(headers)` before
calling mockResolvedValue or mockRejectedValue. Update all occurrences in the
existing test cases while preserving their mocked header behavior and strict
typing.

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/__tests__/auth-actions.test.ts`:
- Around line 32-34: Update the auth action test setup to import _clearBuckets
from `@/lib/rate-limit` and invoke it in beforeEach alongside the existing mock
and mockSignToken resets, ensuring signin:ip:unknown state is cleared between
tests; optionally add a regression test covering the duplicate-action limit
state.

---

Nitpick comments:
In `@src/lib/__tests__/rate-limit.test.ts`:
- Around line 60-146: Replace every `(headers as any)` cast in the rate-limit
tests with `vi.mocked(headers)` before calling mockResolvedValue or
mockRejectedValue. Update all occurrences in the existing test cases while
preserving their mocked header behavior and strict typing.
🪄 Autofix (Beta)

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: 59e01813-1882-45de-bd6f-3736983d0d33

📥 Commits

Reviewing files that changed from the base of the PR and between 9d0e0bf and b401813.

📒 Files selected for processing (6)
  • .jules/sentinel.md
  • src/__tests__/setup.ts
  • src/app/actions/__tests__/auth-actions.test.ts
  • src/app/actions/auth.ts
  • src/lib/__tests__/rate-limit.test.ts
  • src/lib/rate-limit.ts

Comment on lines +32 to +34
headers: () => Promise.resolve({
get: () => null,
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether auth-actions.test.ts resets rate-limit bucket state between tests.
fd 'auth-actions.test.ts' src/app/actions/__tests__ --exec cat -n {}

Repository: projectamazonph/amph-v2

Length of output: 7734


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate rate-limit files =="
fd -a 'rate-limit\.ts$' . | sed 's#^\./##'

echo "== inspect src/lib/rate-limit.ts =="
if [ -f src/lib/rate-limit.ts ]; then
  cat -n src/lib/rate-limit.ts
fi

echo "== references to _clearBuckets and rate limit helpers in tests =="
rg -n "_clearBuckets|buckets|rateLimit|rateLimitDual" src/app/actions/__tests__ src/app/actions || true

echo "== auth actions action files =="
fd -a 'auth\.(ts|tsx)$' src/app/actions | sed 's#^\./##'
for f in $(fd 'auth\.(ts|tsx)$' src/app/actions); do
  echo "---- $f ----"
  ast-grep outline "$f" --lang typescript --view expanded || true
  rg -n "rateLimit|rateLimitDual|_clearBuckets|getIp|headers\\(" "$f" || true
done

Repository: projectamazonph/amph-v2

Length of output: 5345


Reset rate-limit buckets between auth action tests.

beforeEach only resets mocks and mockSignToken; it does not clear @/lib/rate-limit’s module-level buckets. Each signInAction call here uses the mocked unknown IP, so hits accumulate in signin:ip:unknown across test cases unless _clearBuckets() runs before each test. Add const { _clearBuckets } = await import('@/lib/rate-limit'); in the test file, clear the buckets in beforeEach, and optionally add a regression test for the duplicate-action limit state.

🤖 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/__tests__/auth-actions.test.ts` around lines 32 - 34, Update
the auth action test setup to import _clearBuckets from `@/lib/rate-limit` and
invoke it in beforeEach alongside the existing mock and mockSignToken resets,
ensuring signin:ip:unknown state is cleared between tests; optionally add a
regression test covering the duplicate-action limit state.

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.

🟡 Not ready to approve

The ip = 'unknown' fallback can create a shared global IP bucket and unintentionally lock out legitimate users in environments where IP headers are unavailable, and the new tests introduce explicit as any casts that conflict with strict TypeScript conventions.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Adds dual (IP + target identifier) rate limiting for auth server actions to reduce brute-force and credential-stuffing risk, with ordering intended to prevent target-bucket pollution from already-blocked IPs.

Changes:

  • Introduce rateLimitDual() that rate-limits by client IP first, then by target identifier.
  • Apply rateLimitDual() to signUpAction and signInAction.
  • Add unit tests for rateLimit/rateLimitDual, and extend next/headers mocks for test environments.
File summaries
File Description
src/lib/rate-limit.ts Adds dual rate-limiting helper that incorporates client IP extraction from request headers.
src/lib/tests/rate-limit.test.ts Adds unit tests covering sliding window behavior and dual-limit ordering.
src/app/actions/auth.ts Switches signup/signin actions to use dual rate limiting.
src/app/actions/tests/auth-actions.test.ts Extends next/headers mock to support server-action code paths using headers.
src/tests/setup.ts Adds a default headers() mock in the global Vitest setup.
.jules/sentinel.md Documents the dual-rate-limit ordering vulnerability and the prevention guidance.
Review details

Suppressed comments (6)

src/lib/tests/rate-limit.test.ts:78

  • Avoid as any casts in tests; use the repo-standard as unknown as ReturnType<typeof vi.fn> cast instead so we keep strict typing consistent.
      (headers as any).mockResolvedValue({

src/lib/tests/rate-limit.test.ts:89

  • Avoid as any casts in tests; use the repo-standard as unknown as ReturnType<typeof vi.fn> cast instead so we keep strict typing consistent.
      (headers as any).mockResolvedValue({

src/lib/tests/rate-limit.test.ts:101

  • Avoid as any casts in tests; use the repo-standard as unknown as ReturnType<typeof vi.fn> cast instead so we keep strict typing consistent.
      (headers as any).mockResolvedValue({

src/lib/tests/rate-limit.test.ts:112

  • Avoid as any casts in tests; use the repo-standard as unknown as ReturnType<typeof vi.fn> cast instead so we keep strict typing consistent.
      (headers as any).mockResolvedValue({

src/lib/tests/rate-limit.test.ts:128

  • Avoid as any casts in tests; use the repo-standard as unknown as ReturnType<typeof vi.fn> cast instead so we keep strict typing consistent.
      (headers as any).mockResolvedValue({

src/lib/tests/rate-limit.test.ts:141

  • Avoid as any casts in tests; use the repo-standard as unknown as ReturnType<typeof vi.fn> cast instead so we keep strict typing consistent.
      (headers as any).mockRejectedValue(new Error('no headers in this environment'));
  • Files reviewed: 7/7 changed files
  • Comments generated: 2
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

describe('rateLimitDual', () => {
it('rate limits on IP first to block abusive actors and prevent target pollution', async () => {
// Mock headers to return IP '1.2.3.4'
(headers as any).mockResolvedValue({
Comment thread src/lib/rate-limit.ts
Comment on lines +80 to +93
// 2. Perform rate-limiting on client IP first to block abusive actors early
// and prevent an IP-blocked attacker from polluting target-based buckets.
const ipKey = `${actionName}:ip:${ip}`;
const ipRl = rateLimit(ipKey, ipLimit, windowMs);
if (!ipRl.allowed) {
return ipRl;
}

// 3. Perform rate-limiting on target ID (e.g., lowercase email)
const targetKey = `${actionName}:target:${targetId.toLowerCase()}`;
const targetRl = rateLimit(targetKey, targetLimit, windowMs);
if (!targetRl.allowed) {
return targetRl;
}
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