You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(security): key per-IP rate limits on the proxy-written forwarded hop
getClientIp derived the client IP from the leftmost X-Forwarded-For entry.
Under any proxy that appends to that header — nginx-ingress, HAProxy,
Cloudflare, and both reference deployments in this repo — the leftmost entry
is supplied by the caller, so rotating it minted a fresh token bucket per
request and every per-IP throttle became a no-op: the contact and demo-request
mailers, telemetry, the docs Ask-AI endpoint, and public-deployment password
attempts.
The repo already treated that hop as untrusted for Better Auth via
AUTH_TRUSTED_PROXIES; Sim's own helper never consulted it. packages/audit
carried a second copy of the same function, forging audit-row IPs.
Resolve the chain right to left instead, skipping configured trusted hops and
returning the first untrusted address — the closest hop the infrastructure
actually vouched for. Shared from @sim/security/client-ip so the app, the docs
app, and the audit package cannot drift again.
- fall back to the rightmost hop, never the leftmost, when every hop is
trusted, so forging an address inside a broad configured range (the docs
recommend 10.0.0.0/16) cannot reinstate the bypass
- strip IPv6 zone ids, which ipaddr accepts at arbitrary length and would
otherwise hand a caller unlimited distinct bucket keys
- canonicalize addresses so equivalent spellings share one bucket
- bound consecutive failed password guesses per deployment, not just per IP,
since a distributed caller gets a fresh IP bucket per source
The generic webhook allowlist keeps leftmost semantics via getAssertedOriginIp:
it names the sending service, not the proxy, so resolving it like a throttle key
would have 403'd every allowlisted delivery. Both sides are now canonicalized.
Operators behind a multi-hop chain should set AUTH_TRUSTED_PROXIES to their real
hops; unset is safe but collapses callers onto the edge address.
# INTERNAL_API_BASE_URL=http://sim-app.default.svc.cluster.local:3000 # Optional: internal URL for server-side /api self-calls; defaults to NEXT_PUBLIC_APP_URL
21
21
# TRUSTED_ORIGINS=https://www.example.com,https://app.example.com # Optional: comma-separated additional public origins to trust for auth (apex+www, alias domains). Merged into Better Auth trustedOrigins.
22
-
# AUTH_TRUSTED_PROXIES=10.0.0.0/24,192.0.2.10 # Optional: reverse-proxy IPs/CIDRs in front of the app. Better Auth walks x-forwarded-for right to left, skips these hops, and uses the first untrusted address as the client IP (prevents forwarded-header spoofing). Use your proxies' actual addresses, not broad private ranges that also cover clients.
22
+
# AUTH_TRUSTED_PROXIES=10.0.0.0/24,192.0.2.10 # Optional: reverse-proxy IPs/CIDRs in front of the app. Better Auth and Sim's own per-IP throttles walk x-forwarded-for right to left, skip these hops, and use the first untrusted address as the client IP (the leftmost entry is caller-supplied and would otherwise let anyone mint a fresh rate-limit bucket per request). Unset trusts no hop and keys on the rightmost entry — safe, but a multi-hop chain collapses callers onto the edge addresses. Use your proxies' actual addresses, not broad private ranges that also cover clients.
23
23
24
24
# Chat (Optional)
25
25
# COPILOT_API_KEY= # Mint one at https://sim.ai. Without it the Sim Chat block, prompt jobs, and Inbox cannot run
0 commit comments