Skip to content

fix(security): trust only configured proxy IPs when deriving client IP - #1306

Open
doncross03 wants to merge 1 commit into
rinafcode:mainfrom
doncross03:fix-1167-trusted-proxy-ip
Open

fix(security): trust only configured proxy IPs when deriving client IP#1306
doncross03 wants to merge 1 commit into
rinafcode:mainfrom
doncross03:fix-1167-trusted-proxy-ip

Conversation

@doncross03

Copy link
Copy Markdown

Overview

This PR closes the rate-limit IP-spoofing hole in getClientIP(). Previously the function trusted x-forwarded-for / x-real-ip headers unconditionally whenever TRUSTED_PROXY_IPS was unset, so any client could forge its IP and rotate rate-limit buckets to bypass limits. Now forwarded headers are trusted only when the request arrives directly from a proxy listed in the TRUSTED_PROXY_IPS allowlist; otherwise they are ignored entirely and every request falls into a shared bucket.

Related Issue

Closes #1167

Changes

🔒 Trusted-Proxy Allowlist

  • [ADD] src/config/environment.ts
    • getTrustedProxyConfig() – resolves the TRUSTED_PROXY_IPS allowlist (typed, read at call time so it is testable).
    • parseTrustedProxyIPs() – comma-separated IPv4/IPv6 parsing with trimming, mirroring the existing getAuthConfig/getJWTConfig patterns.
  • [MODIFY] src/lib/ratelimit.ts
    • getClientIP() now only honors x-forwarded-for / x-real-ip / cf-connecting-ip when the direct connection comes from a configured trusted proxy.
    • When TRUSTED_PROXY_IPS is unset/empty, forwarded headers are never trusted (previous "legacy" fallback removed), so spoofed headers can no longer rotate rate-limit buckets.
  • [MODIFY] .env.example – clarified that leaving TRUSTED_PROXY_IPS unset means forwarded headers are ignored and all requests share one bucket.
  • [MODIFY] Tests updated/added across src/lib/ratelimit.test.ts, src/app/api/tutorials/__tests__/ratelimit.test.ts, src/app/api/errors/report/__tests__/route.test.ts, and src/app/api/courses/__tests__/route.test.ts covering the secure default, trusted-proxy, and spoofing-prevention scenarios.

Verification Results

pnpm vitest run (affected files)
✅ 71/71 passed

pnpm run type-check
✅ No type errors

pnpm run lint
✅ No ESLint warnings or errors

Direct exploit simulation:
✅ Spoofed x-forwarded-for with no proxy configured  -> 127.0.0.1 (single bucket, spoofing blocked)
✅ Legit request via configured trusted proxy        -> real client IP honored
✅ Direct connection with forged headers             -> 127.0.0.1 (spoofing blocked)

Acceptance Criteria

Criteria Status
Trusted-proxy allowlist implemented across the listed files (src/lib/ratelimit.ts, src/config/environment.ts)
Unit/integration tests added or updated and passing ✅ 71/71
No regression; follows project coding standards ✅ type-check + lint clean

getClientIP trusted x-forwarded-for / x-real-ip headers unconditionally
when TRUSTED_PROXY_IPS was unset, letting clients spoof their IP to
rotate rate-limit buckets. Only trust forwarded headers when the request
arrives from a proxy listed in TRUSTED_PROXY_IPS; otherwise ignore them
entirely and fall back to a shared sentinel.

- Add getTrustedProxyConfig()/parseTrustedProxyIPs() to src/config/environment.ts
- Refactor getClientIP() in src/lib/ratelimit.ts to enforce the allowlist
- Update tests for the secure default and trusted-proxy scenarios
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@doncross03 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Well done on the job done so far!
Kindly fix workflow to pass

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.

Trust only configured proxy IPs when deriving client IP

2 participants