Skip to content

🔒 Fix IP Spoofing Vulnerability in Rate Limiter#368

Open
is0692vs wants to merge 1 commit into
mainfrom
security-fix-ip-spoofing-14757583944471206086
Open

🔒 Fix IP Spoofing Vulnerability in Rate Limiter#368
is0692vs wants to merge 1 commit into
mainfrom
security-fix-ip-spoofing-14757583944471206086

Conversation

@is0692vs

@is0692vs is0692vs commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

🎯 What: The vulnerability fixed
The getClientIp function in the rate limiter was vulnerable to IP spoofing. It improperly extracted the right-most IP address from the X-Forwarded-For header.

⚠️ Risk: The potential impact if left unfixed
An attacker could bypass rate limits entirely by manually providing a spoofed X-Forwarded-For header. In a typical reverse-proxy setup, the proxy appends the true client IP to the end of the header. By taking the right-most IP, the application was susceptible to extracting an internal proxy IP or trusting the left-most spoofed IP in certain fallback scenarios, rendering the rate limiter ineffective and opening the application up to DoS attacks.

🛡️ Solution: How the fix addresses the vulnerability
The extraction logic was updated to implement standard proxy-aware parsing:

  1. It reads the X-Forwarded-For comma-separated list from right to left.
  2. It uses a new isTrustedProxy helper to skip over any known private/internal IPs (RFC 1918 and localhost ranges).
  3. It returns the first untrusted (public) IP encountered, which reliably represents the true client IP before any trusted infrastructure routing occurred.
  4. If no untrusted IPs are found, it safely falls back to the left-most IP, or "unknown".
    Test suites were also updated to validate this new secure behavior.

PR created automatically by Jules for task 14757583944471206086 started by @is0692vs

Greptile Summary

このPRは、レートリミッターの getClientIp 関数における IP スプーフィング脆弱性を修正します。X-Forwarded-For ヘッダーを右から左に走査し、RFC 1918 のプライベート範囲をスキップする isTrustedProxy ヘルパーを新たに導入しています。

  • isTrustedProxy を追加して既知の内部/プライベート IP を識別し、X-Forwarded-For を右端から走査して最初の非信頼 IP を真のクライアント IP として返すよう getClientIp を修正。
  • 全 IP が信頼済みの場合のフォールバックとして左端の有効 IP を返し、見つからない場合は "unknown" を返すロジックを追加。
  • テストスイートをアップデートし、信頼済みプロキシスキップ・フォールバック動作を検証する新しいテストケースを追加。

Confidence Score: 3/5

ループバック範囲の不完全なマッチングにより、一部の環境では正しいクライアント IP が特定されない可能性があります。

isTrustedProxy のループバックチェックが 127.0.0.1 のみを対象としており 127.0.0.0/8 全体をカバーしていないため、127.0.0.2 などのアドレスをプロキシとして使用する環境では誤ったクライアント IP がレートリミットのキーとして使われてしまいます。意図としては正しい方向性の修正ですが、この抜け漏れが本番環境で誤動作を招く可能性があります。

src/lib/rateLimit.ts の isTrustedProxy 関数を重点的に確認してください。

Important Files Changed

Filename Overview
src/lib/rateLimit.ts IP スプーフィング対策として isTrustedProxy ヘルパーと右から左への XFF 走査ロジックを追加。ただしループバック範囲が 127.0.0.1 のみに限定され 127.0.0.0/8 全体をカバーしていない点、および IPv6 ULA の fc プレフィックス検出が不完全な点に問題あり。
src/lib/tests/rateLimit.test.ts 新しいセキュリティ修正に合わせてテストを更新。信頼済みプロキシスキップ・フォールバック動作のテストケースを追加。127.0.0.0/8 全体のカバレッジテストは含まれていない。

Sequence Diagram

sequenceDiagram
    participant Client as クライアント (203.0.113.1)
    participant Proxy as プロキシ (10.0.0.1)
    participant App as アプリケーション
    participant getClientIp as getClientIp()
    participant isTrustedProxy as isTrustedProxy()

    Client->>Proxy: リクエスト送信
    Note over Proxy: XFF に接続元 IP を追記
    Proxy->>App: X-Forwarded-For: 203.0.113.1, 10.0.0.1

    App->>getClientIp: request を渡す
    getClientIp->>getClientIp: IPs を右から走査
    getClientIp->>isTrustedProxy: "10.0.0.1" をチェック
    isTrustedProxy-->>getClientIp: true (プライベート IP)
    getClientIp->>isTrustedProxy: "203.0.113.1" をチェック
    isTrustedProxy-->>getClientIp: false (パブリック IP)
    getClientIp-->>App: "203.0.113.1" を返す
    App->>App: RateLimiter.check("203.0.113.1")
Loading
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
src/lib/rateLimit.ts:71
**ループバックアドレス範囲の不完全なマッチング**

`127\.0\.0\.1` は単一のアドレスのみにマッチしますが、ループバック範囲 `127.0.0.0/8` 全体(`127.0.0.1``127.255.255.255`)をカバーしていません。`127.0.0.2` など `127.0.0.1` 以外のループバックアドレスをプロキシとして使用している環境では、そのアドレスが「信頼済みプロキシ」とみなされず、誤ってクライアント IP として返されます。結果として、そのプロキシを経由するすべてのリクエストが同一のレートリミットバケットに集約され、正当なユーザーが不当にブロックされる可能性があります。

### Issue 2 of 3
src/lib/rateLimit.ts:73
**IPv6 ULA の fc プレフィックス範囲が不完全**

`fc00::` というパターンは `fc00::` で始まるアドレス(例: `fc00::1`)にしかマッチせず、`fc01::1``fc12::1` のような `fc00::/7` 範囲内の他の `fc` プレフィックスアドレスをカバーできていません。RFC 4193 の ULA 範囲 `fc00::/7``fc``fd` の両方のプレフィックスを含むため、`fc` 単体のパターンに変更すべきです。

```suggestion
    const privateIpv6 = /^(::1|fc|fd)/;
```

### Issue 3 of 3
src/lib/rateLimit.ts:85-86
**"For this example" は本番コードに残すべきでないコメント**

コメント内の「For this example」という表現は、プロトタイプや例示コードの名残のような印象を与えます。本番コードにはより正確な意図を示す表現を使用すべきです。

```suggestion
    // 右端から左に向かって走査し、最初の非プライベート(非信頼)IPを真のクライアントIPとして返す。
```

Reviews (1): Last reviewed commit: "fix(security): resolve IP spoofing vulne..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Co-authored-by: is0692vs <135803462+is0692vs@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.

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
github-user-summary Ignored Ignored Jun 12, 2026 6:53am

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@is0692vs, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 minutes. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4df7294b-293b-46ba-a88f-aa8475858041

📥 Commits

Reviewing files that changed from the base of the PR and between a475ecb and f3430a1.

📒 Files selected for processing (2)
  • src/lib/__tests__/rateLimit.test.ts
  • src/lib/rateLimit.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security-fix-ip-spoofing-14757583944471206086

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.

@dosubot dosubot Bot added the bug Something isn't working label Jun 12, 2026
@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/lib/rateLimit.ts 90.00% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request updates the getClientIp function to identify the true client IP by skipping trusted proxies (private and internal IPs) when parsing the x-forwarded-for header, iterating from right to left. It also adds a helper function isTrustedProxy and updates the test suite. The review feedback points out critical security and correctness issues in the isTrustedProxy regex matching, such as case-sensitivity for IPv6 addresses and incomplete coverage for loopback and link-local ranges, and provides a robust code suggestion to address these vulnerabilities.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/lib/rateLimit.ts
Comment on lines +69 to +76
function isTrustedProxy(ip: string): boolean {
// Matches standard private IPv4 ranges (RFC 1918) and localhost
const privateIpv4 = /^(127\.0\.0\.1|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/;
// Matches IPv6 localhost
const privateIpv6 = /^(::1|fd|fc00::)/;

return privateIpv4.test(ip) || privateIpv6.test(ip);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-high high

The isTrustedProxy function has several correctness and security issues:

  1. Case Sensitivity: IPv6 addresses can be uppercase (e.g., FD00::1). The regex /^(::1|fd|fc00::)/ is case-sensitive and will fail to match uppercase IPv6 addresses, potentially allowing rate-limiting bypasses or causing Denial of Service (DoS) if trusted proxies are misclassified as untrusted client IPs.
  2. Incomplete IPv6 ULA / Link-Local Matching:
    • fc00:: only matches addresses starting with exactly fc00:: (e.g., fc00::1), but fails to match other valid Unique Local Addresses (ULA) like fc01::1 or fc00:1::1 (since ULA is fc00::/7, which covers fc00:: to fdff::).
    • Link-local IPv6 addresses (fe80::/10) are not matched at all.
  3. Incomplete IPv4 Loopback / Link-Local Matching:
    • Only 127.0.0.1 is matched, but the entire 127.0.0.0/8 block is reserved for loopback (e.g., 127.0.0.2).
    • Link-local IPv4 addresses (169.254.0.0/16) are not matched.

We can fix these issues by updating the regexes to be case-insensitive and cover the full ranges.

Suggested change
function isTrustedProxy(ip: string): boolean {
// Matches standard private IPv4 ranges (RFC 1918) and localhost
const privateIpv4 = /^(127\.0\.0\.1|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/;
// Matches IPv6 localhost
const privateIpv6 = /^(::1|fd|fc00::)/;
return privateIpv4.test(ip) || privateIpv6.test(ip);
}
function isTrustedProxy(ip: string): boolean {
// Matches standard private IPv4 ranges (RFC 1918), loopback (127.0.0.0/8), and link-local (169.254.0.0/16)
const privateIpv4 = /^(127\.|10\.|192\.168\.|169\.254\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/;
// Matches IPv6 loopback (::1), Unique Local Addresses (fc00::/7), and Link-Local Addresses (fe80::/10)
const privateIpv6 = /^(::1|f[cd]|fe[89ab])/i;
return privateIpv4.test(ip) || privateIpv6.test(ip);
}

Comment thread src/lib/rateLimit.ts

function isTrustedProxy(ip: string): boolean {
// Matches standard private IPv4 ranges (RFC 1918) and localhost
const privateIpv4 = /^(127\.0\.0\.1|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/;

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.

P1 ループバックアドレス範囲の不完全なマッチング

127\.0\.0\.1 は単一のアドレスのみにマッチしますが、ループバック範囲 127.0.0.0/8 全体(127.0.0.1127.255.255.255)をカバーしていません。127.0.0.2 など 127.0.0.1 以外のループバックアドレスをプロキシとして使用している環境では、そのアドレスが「信頼済みプロキシ」とみなされず、誤ってクライアント IP として返されます。結果として、そのプロキシを経由するすべてのリクエストが同一のレートリミットバケットに集約され、正当なユーザーが不当にブロックされる可能性があります。

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/rateLimit.ts
Line: 71

Comment:
**ループバックアドレス範囲の不完全なマッチング**

`127\.0\.0\.1` は単一のアドレスのみにマッチしますが、ループバック範囲 `127.0.0.0/8` 全体(`127.0.0.1``127.255.255.255`)をカバーしていません。`127.0.0.2` など `127.0.0.1` 以外のループバックアドレスをプロキシとして使用している環境では、そのアドレスが「信頼済みプロキシ」とみなされず、誤ってクライアント IP として返されます。結果として、そのプロキシを経由するすべてのリクエストが同一のレートリミットバケットに集約され、正当なユーザーが不当にブロックされる可能性があります。

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/lib/rateLimit.ts
// Matches standard private IPv4 ranges (RFC 1918) and localhost
const privateIpv4 = /^(127\.0\.0\.1|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/;
// Matches IPv6 localhost
const privateIpv6 = /^(::1|fd|fc00::)/;

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.

P2 IPv6 ULA の fc プレフィックス範囲が不完全

fc00:: というパターンは fc00:: で始まるアドレス(例: fc00::1)にしかマッチせず、fc01::1fc12::1 のような fc00::/7 範囲内の他の fc プレフィックスアドレスをカバーできていません。RFC 4193 の ULA 範囲 fc00::/7fcfd の両方のプレフィックスを含むため、fc 単体のパターンに変更すべきです。

Suggested change
const privateIpv6 = /^(::1|fd|fc00::)/;
const privateIpv6 = /^(::1|fc|fd)/;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/rateLimit.ts
Line: 73

Comment:
**IPv6 ULA の fc プレフィックス範囲が不完全**

`fc00::` というパターンは `fc00::` で始まるアドレス(例: `fc00::1`)にしかマッチせず、`fc01::1``fc12::1` のような `fc00::/7` 範囲内の他の `fc` プレフィックスアドレスをカバーできていません。RFC 4193 の ULA 範囲 `fc00::/7``fc``fd` の両方のプレフィックスを含むため、`fc` 単体のパターンに変更すべきです。

```suggestion
    const privateIpv6 = /^(::1|fc|fd)/;
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/lib/rateLimit.ts
Comment on lines +85 to +86
// For this example, we assume we want to skip internal/private IPs (trusted proxies)
// and find the true client IP.

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.

P2 "For this example" は本番コードに残すべきでないコメント

コメント内の「For this example」という表現は、プロトタイプや例示コードの名残のような印象を与えます。本番コードにはより正確な意図を示す表現を使用すべきです。

Suggested change
// For this example, we assume we want to skip internal/private IPs (trusted proxies)
// and find the true client IP.
// 右端から左に向かって走査し、最初の非プライベート(非信頼)IPを真のクライアントIPとして返す。
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/rateLimit.ts
Line: 85-86

Comment:
**"For this example" は本番コードに残すべきでないコメント**

コメント内の「For this example」という表現は、プロトタイプや例示コードの名残のような印象を与えます。本番コードにはより正確な意図を示す表現を使用すべきです。

```suggestion
    // 右端から左に向かって走査し、最初の非プライベート(非信頼)IPを真のクライアントIPとして返す。
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant