Sanitized the Content-Type header on image and video proxy routes (`/api/image/*`, `/api/video/*`). The content type is forced to lowercase, validated against a safe prefix (`image/`, `video/`, `audio/`), and restricted to block `svg` and `xml`. Any invalid type falls back to `application/octet-stream`. This prevents an attacker from uploading an SVG file containing a malicious script and triggering a Stored XSS attack via these proxy endpoints, which are excluded from the global CSP. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
5.6 KiB
2024-05-18 - Unprotected Dynamic Image Generation Endpoints
Vulnerability: The /api/og route generates an ImageResponse dynamically using next/og. Because this operation is computationally expensive and memory-intensive, unauthenticated endpoints lacking rate limiting act as prominent Denial of Service (DoS) vectors. Attackers can flood the endpoint with varying parameters, forcing the server to continually spawn compute-heavy tasks until resources are exhausted and the instance crashes or latency spikes to unacceptable levels.
Learning: Next.js dynamic endpoints that do not hit external upstream APIs or databases (like image generation with next/og) are often overlooked for rate limiting. Rate limiting is just as critical for protecting local compute resources as it is for protecting downstream API quotas or databases. Any route performing on-the-fly heavy processing must implement throttling.
Prevention: Apply the shared checkRateLimit utility (from @/lib/rate-limit) to all computationally expensive endpoints (such as next/og usage), even if they do not explicitly query external services. Ensure getConfig().rateLimitRpm is passed as the threshold to maintain configurable global protection.
2024-05-18 - [Preventing Timing Attacks on Variable-Length Secrets]
Vulnerability: Comparing variable-length plaintext passwords using strict equality (===) or crypto.timingSafeEqual with unequal lengths, which leaks length information and enables timing attacks.
Learning: When comparing variable-length secrets (like plaintext fallback passwords), both values must be hashed to a fixed-length algorithm (like SHA-256) first to ensure identical input lengths for the secure comparison, preventing side-channel leaks.
Prevention: Always hash variable-length secrets to a fixed length before passing them to crypto.timingSafeEqual.
2024-05-18 - Rate Limit Store Flooding and Cache Eviction Vulnerability
Vulnerability: The rate limiter implemented an LRU/FIFO map to track API request limits, evicting the oldest tracked IP when maximum capacity was reached. An attacker could flood the store with randomly spoofed IP headers, continuously evicting legitimate users' IP addresses. This allowed the attacker to effectively bypass their own rate limit and subject the upstream server to DoS. Learning: Evicting entries from a security control store (like a rate limit map) under pressure turns the limit into a revolving door if an attacker controls the keys (e.g., via IP spoofing headers). Prevention: Rather than evicting old entries when an in-memory rate limit store is full, the correct security posture is to fail closed (or block new keys) until entries naturally expire, preserving tracking on existing keys.
2026-04-23 - Prevent Global DoS in Rate Limiter via FIFO Eviction
Vulnerability: The in-memory rate limiter implemented a hard ceiling (MAX_STORE_ENTRIES) and rejected all new IPs when reached, creating a trivial Global Denial of Service (DoS) vulnerability where an attacker could spam spoofed IPs to fill the bounded Map and permanently block all legitimate traffic.
Learning: "Failing securely" by denying unknown requests during capacity limits is incorrect for rate limiters. Bounded Maps must actively evict older entries rather than rejecting new ones, otherwise the security mechanism itself becomes the vector for DoS.
Prevention: In-memory rate limiters using bounded Maps must implement a FIFO (First-In-First-Out) or LRU (Least Recently Used) eviction policy (e.g., deleting store.keys().next().value) to make room for new IPs when maximum capacity is reached.
2024-05-18 - Incorrect Scrypt Hash Generation in Plaintext Fallback
Vulnerability: The plaintext password fallback logic incorrectly generated a recommended scrypt hash using the SHA256 hashed password rather than the raw storedPassword. This leads to generating an invalid scrypt hash that would fail authentication if the user updated their configuration with it. Furthermore, hashing variable-length secrets with SHA256 before crypto.timingSafeEqual prevents timing attacks, but using a non-keyed hash like SHA256 may leave it vulnerable. HMAC-SHA256 keyed with a secret provides better security.
Learning: When generating a secure hash recommendation for users migrating from plaintext, ensure the raw input is used, not an intermediate hash intended for timing-safe comparison.
Prevention: Generate the scrypt hash directly from the raw secret string (storedPassword). Always use HMAC keyed with a secret for hashing variable-length passwords prior to constant-time comparison.
2024-05-30 - Prevent Stored XSS via Content-Type Validation on Proxied Assets
Vulnerability: Asset proxy routes (/api/image/*, /api/video/*) directly served the upstream Content-Type header without validation. Since these API routes are explicitly excluded from the Next.js Content Security Policy (CSP) matcher, returning types like image/svg+xml or text/html would execute scripts directly in the user's browser, leading to Stored XSS.
Learning: Endpoints that serve user-controlled content and are not protected by global CSP headers require strict MIME-type validation. An attacker could upload an SVG containing malicious scripts and retrieve it via these proxy endpoints to bypass standard security controls.
Prevention: Always sanitize the Content-Type header on proxy endpoints. First convert it to lowercase, remap required generic types (like application/octet-stream to image/jpeg), and finally enforce a strict positive allowlist (startsWith('image/') and !includes('svg')) before falling back to application/octet-stream.