From 14583056040e3f9beb1f9a9367a51f1e1e3aa0f9 Mon Sep 17 00:00:00 2001 From: CahidArda Date: Mon, 31 Aug 2026 16:59:42 +0300 Subject: [PATCH] docs(security): rate limiting must count in a shared store across instances --- skills/security-and-hardening/SKILL.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/skills/security-and-hardening/SKILL.md b/skills/security-and-hardening/SKILL.md index c00236e9..af35e27e 100644 --- a/skills/security-and-hardening/SKILL.md +++ b/skills/security-and-hardening/SKILL.md @@ -329,6 +329,20 @@ app.use('/api/auth/', rateLimit({ })); ``` +**Count in a shared store once there is more than one process.** `express-rate-limit` keeps its counters in process memory by default. Behind a load balancer each instance holds its own count, so the effective limit is `max × instances`; on serverless or edge runtimes a fresh invocation starts from zero, so the auth limit above may never fire. Pass a shared `store` (Redis via `rate-limit-redis`), or use an HTTP-based limiter that works where a long-lived TCP connection does not (for example `@upstash/ratelimit`): + +```typescript +import { Ratelimit } from '@upstash/ratelimit'; +import { Redis } from '@upstash/redis'; + +const authLimiter = new Ratelimit({ + redis: Redis.fromEnv(), // UPSTASH_REDIS_REST_URL + _TOKEN + limiter: Ratelimit.slidingWindow(10, '15 m'), // 10 attempts per 15 minutes, across all instances +}); +const { success } = await authLimiter.limit(`login:${req.ip}`); +if (!success) return res.status(429).end(); +``` + ## Secrets Management ``` @@ -472,7 +486,7 @@ For detailed security checklists and pre-commit verification steps, see `../../r - Secrets in source code or commit history - API endpoints without authentication or authorization checks - Missing CORS configuration or wildcard (`*`) origins -- No rate limiting on authentication endpoints +- No rate limiting on authentication endpoints, or an in-memory limiter in front of more than one instance - Stack traces or internal errors exposed to users - Dependencies with known critical vulnerabilities, competing lockfiles at one installation boundary, non-reproducible installs, or blanket-approved scripts - Server fetches user-supplied URLs without an allowlist (SSRF) @@ -492,7 +506,7 @@ After implementing security-relevant code: - [ ] Authentication and authorization checked on every protected endpoint - [ ] Security headers present in response (check with browser DevTools) - [ ] Error responses don't expose internal details -- [ ] Rate limiting active on auth endpoints +- [ ] Rate limiting active on auth endpoints, backed by a shared store when more than one instance serves traffic - [ ] Server-side URL fetches validated against an allowlist (no SSRF) - [ ] LLM/model output validated and encoded before use (if AI features present) - [ ] Personal data is classified, minimized to a stated purpose, and has a retention limit