fix(auth): resolve client IP from proxy headers for pre-auth rate limits - #478
Conversation
|
Note Gittensory Gate skippedPR closed before full evaluation. No late first comment was created.
Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers. |
JSONbored
left a comment
There was a problem hiding this comment.
@andriypolanski this is the right pre-auth rate-limit path, but the fallback still accepts unsafe header values.
A few notes:
- The implementation takes the first non-empty x-forwarded-for / x-real-ip value. It does not validate that the value is an actual IP address before using it as the rate-limit key.
- For a pre-auth limiter, malformed or spoof-looking forwarded values should not be allowed to create arbitrary buckets.
- Superagent is also action-required on this PR, but the source-level blocker above is enough on its own.
Required changes:
- Validate cf-connecting-ip, x-forwarded-for candidates, and x-real-ip candidates before accepting them.
- Use the first valid IP in x-forwarded-for, not just the first non-empty token.
- Fall back to the next candidate, then unknown-ip, when a header is blank or malformed.
- Add negative tests for malformed forwarded values and mixed valid/invalid x-forwarded-for chains.
Validation expected:
- focused auth/rate-limit tests
- full validate
JSONbored
left a comment
There was a problem hiding this comment.
@andriypolanski this is still on the right path, but the main pre-auth safety issue is not fixed yet.
A few notes:
- Falling back from
cf-connecting-iptox-forwarded-forandx-real-ipaddresses the sharedunknown-ipproblem from #477. - The implementation still accepts the first non-empty forwarded value without validating that it is an IP address.
- For a pre-auth limiter, malformed or attacker-controlled strings should not create arbitrary rate-limit buckets.
Required changes:
- Validate
cf-connecting-ip,x-forwarded-for, andx-real-ipcandidates as IP addresses before using them in the rate-limit key. - Ignore malformed forwarded values and continue to the next candidate, falling back to
unknown-iponly when no usable IP exists. - Add negative tests for malformed
x-forwarded-for, malformedx-real-ip, comma-separated forwarded values, and fallback ordering.
Validation expected:
- focused
test/unit/auth.test.ts - full
validateor at leastnpm run typecheckplus coverage for the new negative paths
5231f9d to
2bc2530
Compare
JSONbored
left a comment
There was a problem hiding this comment.
Approving — the current head resolves the prior change-requests. The pre-auth rate-limit key now validates client-address headers before use, which was the outstanding blocker.
clientIp (src/auth/rate-limit.ts) now:
- Tries
cf-connecting-ip, then each comma-splitx-forwarded-fortoken, thenx-real-ip, returning the first value that passesnormalizeIpAddressand falling back tounknown-iponly when none is a valid IP. - Validates IPv4 (4 octets, 0-255) and IPv6 (segment count, single
::, hex-only segments, bracket stripping), so malformed/spoof-looking forwarded strings can no longer mint arbitrary rate-limit buckets — the original #477 concern and the follow-up validation requirement are both met.
Tests cover the fallback ordering, first-valid-token selection within an XFF chain, malformed cf-connecting-ip / XFF / x-real-ip falling through, all-invalid collapsing to a shared unknown-ip bucket, and IPv6 bracket normalization keying identically. CI validate and Superagent are green on the current head.
Two non-blocking notes for a future pass:
- The
package-lock.jsonchurn (addingdev: true/ removinglibcentries) is unrelated lockfile metadata from a local install; consider reverting it to keep the PR focused, though it is harmless. isValidIpv4accepts leading-zero octets (e.g.01.02.03.04); only a cosmetic key-canonicalization nit, not a security issue, since both forms still validate to a stable bucket.
Note: I'm an external reviewer and cannot merge; leaving the merge decision to @JSONbored, who has the standing review on this PR.
|
reviewbot · advisory review Reviewed 4 changed file(s) — two independent AI reviewers. Suggested action: ✅ Safe to merge — both reviewers found no blocking issues. Reviewer A · Suggestions
Worth double-checking
Reviewer B · Worth double-checking
|
c18731b to
69ff97a
Compare
683013b to
da3c8af
Compare
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Summary
Fixes #477: pre-auth rate limits no longer collapse every anonymous client into a shared
unknown-ipbucket when Cloudflare'scf-connecting-ipheader is absent.clientIpnow resolves client identity in proxy-aware order:cf-connecting-ip(unchanged preference for Cloudflare edge)x-forwarded-forx-real-ip"unknown-ip"only when no usable address header is presentThis restores fair per-client isolation on auth, webhook, MCP, and other pre-auth routes for local deployments, preview proxies, and any path that forwards standard client-address headers without injecting
cf-connecting-ip.Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally; global coverage stays at or above 97% for lines, statements, functions, and branches (aim for 98%+ branch coverage locally so CI variance does not fail near the threshold)npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateIf any required check was skipped, explain why:
Safety
UI Evidencesection below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails. SVG screenshots are not used as review evidence. Review-only screenshots or recordings are not committed to the repository.Test plan
x-forwarded-forvalues and nocf-connecting-ipproduce different rate-limit keys.x-forwarded-for: "198.51.100.2, 198.51.100.3"keys on the first forwarded address (198.51.100.2).x-real-ipis used when bothcf-connecting-ipandx-forwarded-forare absent.x-forwarded-for/x-real-ipvalues fall back tounknown-ipinstead of using attacker-controlled empty strings as bucket keys.cf-connecting-ipstill wins when present; same Cloudflare IP with different bearer tokens still shares one pre-auth bucket.Notes
Suggested PR title:
fix(auth): resolve client IP from proxy headers for pre-auth rate limits