Summary
Rate-limited IDP routes use `(request) => request.ip` as the keyGenerator. Because the Fastify server is configured with `trustProxy: true`, `request.ip` is derived from `X-Forwarded-For` when present. In any deployment where JSS is not behind a proxy (or is misconfigured behind one that doesn't strip client-supplied `X-Forwarded-For`), an attacker can rotate that header to bypass the per-IP limit.
Affected endpoints (today):
Anywhere else that uses the same `config.rateLimit.keyGenerator: (request) => request.ip` pattern.
Why this matters
Brute-force protection is the only thing standing between bcrypt and a credential-stuffing attacker. A spoofable key generator effectively neutralizes it.
Options
A. Trust the TCP-level address
```js
keyGenerator: (request) => request.socket.remoteAddress
```
- Cannot be spoofed from a remote attacker
- Breaks behind a real reverse proxy: every request appears to come from the proxy IP, so legit users share a single rate-limit bucket
B. Tighten `trustProxy` to known proxies
Configure `trustProxy` with a list of trusted IPs (e.g. `['127.0.0.1', '::1']`) instead of the boolean `true`. `request.ip` then only honors `X-Forwarded-For` from those addresses.
- Correct under both deployment modes (proxied + non-proxied)
- Requires the operator to configure proxy IPs explicitly
C. Document the deployment requirement
Add a deployment note: "If running JSS without a reverse proxy, set `trustProxy: false`."
- Cheapest fix, but easy to miss
Recommendation
B + C combined. Default `trustProxy` to `'loopback'` (Fastify supports this string shorthand) so localhost proxies work out of the box but external `X-Forwarded-For` is ignored. Operators with non-loopback proxies set the list explicitly. Document in deployment notes.
Acceptance
Context
Spotted by Copilot on PR #355. Predates that PR — same pattern is on existing `POST` routes. Filing for a server-wide fix rather than touching it just on the new `PUT`.
Summary
Rate-limited IDP routes use `(request) => request.ip` as the keyGenerator. Because the Fastify server is configured with `trustProxy: true`, `request.ip` is derived from `X-Forwarded-For` when present. In any deployment where JSS is not behind a proxy (or is misconfigured behind one that doesn't strip client-supplied `X-Forwarded-For`), an attacker can rotate that header to bypass the per-IP limit.
Affected endpoints (today):
Anywhere else that uses the same `config.rateLimit.keyGenerator: (request) => request.ip` pattern.
Why this matters
Brute-force protection is the only thing standing between bcrypt and a credential-stuffing attacker. A spoofable key generator effectively neutralizes it.
Options
A. Trust the TCP-level address
```js
keyGenerator: (request) => request.socket.remoteAddress
```
B. Tighten `trustProxy` to known proxies
Configure `trustProxy` with a list of trusted IPs (e.g. `['127.0.0.1', '::1']`) instead of the boolean `true`. `request.ip` then only honors `X-Forwarded-For` from those addresses.
C. Document the deployment requirement
Add a deployment note: "If running JSS without a reverse proxy, set `trustProxy: false`."
Recommendation
B + C combined. Default `trustProxy` to `'loopback'` (Fastify supports this string shorthand) so localhost proxies work out of the box but external `X-Forwarded-For` is ignored. Operators with non-loopback proxies set the list explicitly. Document in deployment notes.
Acceptance
Context
Spotted by Copilot on PR #355. Predates that PR — same pattern is on existing `POST` routes. Filing for a server-wide fix rather than touching it just on the new `PUT`.