fix(server): trust proxy and trusted client IP for redirect and attribution#10
Conversation
…ening for deployments behind reverse proxies, CDNs, and load balancers.Changes:- Add configurable Fastify trustProxy support via ServerOptions and TRUST_PROXY env- Introduce getClientIp() helper for consistent client IP extraction- Update redirect and SDK routes to rely on trusted server-derived IP- Prevent spoofed client-provided ipAddress from being used for attribution- Add unit tests for proxy scenarios and spoofed SDK install requests- Document proxy behavior and TRUST_PROXY configuration
onamfc
left a comment
There was a problem hiding this comment.
Great contribution — this is a meaningful security hardening. The core fix (no longer trusting client-provided body.ipAddress for attribution) is exactly right, and the getClientIp helper is clean and well-tested.
A couple minor suggestions for future consideration (not blocking merge):
-
Type safety on socket fallback —
(request as any).socket?.remoteAddresscould berequest.raw.socket?.remoteAddressto avoid theas anycast and use Fastify's typedIncomingMessage. -
'unknown'fallback — Worth noting that'unknown'is truthy and flows throughnormalizeIPunchanged, which means two installs with no IP would match on the IP fingerprint factor (40 points). Not introduced by this PR, but something to be aware of. An empty string would be safer sincenormalizeIPalready returns''for falsy inputs.
Tests are solid — the spoof prevention test is exactly the kind of coverage this change needs. Merging!
Description
Harden production use when the app runs behind reverse proxies, CDNs, or load balancers. The platform relies on client IP for redirect targeting, attribution confidence, and fingerprint matching. Without trust proxy,
request.ipcan be the proxy IP; and the SDK install endpoint previously trusted client-providedipAddressin the body, which is spoofable. This change adds configurable proxy trust, a single helper for trusted client IP, and ensures client body IP is never used for trust.Type of Change
Changes Made
trustProxytoServerOptionsand pass it intoFastify()sorequest.ipcan be taken fromX-Forwarded-Forwhen behind a proxy.getClientIp(request)insrc/lib/client-ip.ts(with IPv6-mapped unwrap and fallback tosocket.remoteAddressor'unknown').getClientIp(request)for targeting and click tracking instead ofrequest.ip.getClientIp(request)for attribution/fingerprint; keep optionalbody.ipAddressas debug-only and return it asclientReportedIpwhen present.getClientIp(request)for click recording.TRUST_PROXYenv support in.env.exampleandexamples/basic-server.ts(e.g.TRUST_PROXY=1or hop count).src/lib/client-ip.test.ts(getClientIp unit tests, proxied request with trustProxy, SDK install spoof test asserting body IP is not trusted).TRUST_PROXYand that clientipAddressis not trusted.Testing
npm run build— succeeds.npm run test— all 27 tests pass, including 7 new client-ip tests (direct IP, socket fallback, IPv6-mapped, unknown, trustProxy + X-Forwarded-For, SDK install spoof).Related Issues
N/A (production hardening as requested; no issue number).