Skip to content

feat: opt-in per-IP rate limiting on /validate via RATE_LIMIT_RPM#19

Merged
SINENSIA merged 1 commit into
mainfrom
feat/rate-limiting
May 10, 2026
Merged

feat: opt-in per-IP rate limiting on /validate via RATE_LIMIT_RPM#19
SINENSIA merged 1 commit into
mainfrom
feat/rate-limiting

Conversation

@scops
Copy link
Copy Markdown
Collaborator

@scops scops commented May 10, 2026

Summary

Adds opt-in per-IP rate limiting on POST /validate, controlled by the new RATE_LIMIT_RPM env var. Disabled by default — when unset the middleware is a no-op pass-through.

  • RATE_LIMIT_RPM=N → at most N requests per minute per req.ip to /validate.
  • Exceeded → 429 Too Many Requests with body { safe: false, error: "Rate limit exceeded" } plus retry-after and ratelimit-* headers (RFC 9462).
  • /health and /openapi.json are never rate-limited so probes and introspection keep working under load.
  • Invalid values (0, negative, non-integer, NaN) throw at startup. No silent fallback.

Why

The README has always said the service "expects to live behind a gateway that handles rate limiting." That remains true, but defence-in-depth is cheap: a CPU-spike on the sanitizer is a real concern for adversarial payloads, and express-rate-limit adds it with one env var. Keeping it opt-in preserves the "do one thing well" disposition.

Notes

  • The limiter keys on req.ip. Behind a reverse proxy, operators should configure app.set('trust proxy', ...) so the real client IP is used. The service ships with no trust-proxy config because that's a topology-specific decision and getting it wrong is a header-injection vector. Documented in the README.
  • Used standardHeaders: 'draft-7' to emit the RFC 9462 ratelimit family instead of the legacy x-rate-limit-* set.
  • OpenAPI gains a 429 response entry on /validate; the contract test was tightened to assert it.

Test plan

  • npm test — 48/48 (9 new in tests/rate-limit.test.js):
    • default unset → no limiting
    • exceeded limit → 429 with retry-after
    • /health and /openapi.json unaffected at RATE_LIMIT_RPM=1
    • parameterised: 0, -1, 1.5, abc all throw at startup; empty string treated as unset
  • CI green on Node 20/22/24, audit, CodeQL.

@SINENSIA SINENSIA merged commit 711180a into main May 10, 2026
6 checks passed
@SINENSIA SINENSIA deleted the feat/rate-limiting branch May 10, 2026 21:28
@scops scops mentioned this pull request May 10, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants