Where: src/middleware/rateLimit.ts (uses express-rate-limit's default
keyGenerator, which reads req.ip), and the absence of any
app.set("trust proxy", ...) call anywhere in src/index.ts or elsewhere.
What's wrong: without trust proxy configured, Express's req.ip
resolves to req.socket.remoteAddress — the direct TCP peer. In any
production deployment that sits behind a reverse proxy / load balancer / CDN
(the common case for a Node backend, and implied by the codebase's own
X-Forwarded-For-reading code in ipWhitelist.ts), req.socket.remoteAddress
is the proxy's IP for every single client. publicLimiter/adminLimiter
(src/middleware/rateLimit.ts) therefore key every client into the same
rate-limit bucket in that topology.
Impact: in production behind a typical proxy, one heavy or abusive client
exhausts the shared rate-limit bucket for all clients (denial of service
for legitimate traffic), while the whole point of per-IP rate limiting —
isolating one bad actor — silently doesn't work. This is the flip side of
the ipWhitelist.ts X-Forwarded-For issue: that one is unsafe because it
manually trusts a spoofable header without proxy config; this one is broken
because express-rate-limit correctly doesn't trust anything without proxy
config, so it under-trusts in the real deployment topology.
Suggested fix: configure app.set("trust proxy", ...) to the exact
number of trusted hops (or a specific CIDR) matching the real deployment,
then let both express-rate-limit and ipWhitelist.ts derive the client IP
from Express's own req.ip instead of hand-parsing headers.
Where:
src/middleware/rateLimit.ts(usesexpress-rate-limit's defaultkeyGenerator, which readsreq.ip), and the absence of anyapp.set("trust proxy", ...)call anywhere insrc/index.tsor elsewhere.What's wrong: without
trust proxyconfigured, Express'sreq.ipresolves to
req.socket.remoteAddress— the direct TCP peer. In anyproduction deployment that sits behind a reverse proxy / load balancer / CDN
(the common case for a Node backend, and implied by the codebase's own
X-Forwarded-For-reading code inipWhitelist.ts),req.socket.remoteAddressis the proxy's IP for every single client.
publicLimiter/adminLimiter(
src/middleware/rateLimit.ts) therefore key every client into the samerate-limit bucket in that topology.
Impact: in production behind a typical proxy, one heavy or abusive client
exhausts the shared rate-limit bucket for all clients (denial of service
for legitimate traffic), while the whole point of per-IP rate limiting —
isolating one bad actor — silently doesn't work. This is the flip side of
the
ipWhitelist.tsX-Forwarded-For issue: that one is unsafe because itmanually trusts a spoofable header without proxy config; this one is broken
because
express-rate-limitcorrectly doesn't trust anything without proxyconfig, so it under-trusts in the real deployment topology.
Suggested fix: configure
app.set("trust proxy", ...)to the exactnumber of trusted hops (or a specific CIDR) matching the real deployment,
then let both
express-rate-limitandipWhitelist.tsderive the client IPfrom Express's own
req.ipinstead of hand-parsing headers.