Merge PR 36 and resolve ci.yml conflict

This commit is contained in:
ralksta
2026-03-22 10:03:02 +01:00
2 changed files with 27 additions and 1 deletions

View File

@@ -3,3 +3,8 @@
**Vulnerability:** The `/api/exif/[id]` endpoint lacked rate limiting, making it vulnerable to DoS attacks. An attacker could rapidly request EXIF metadata, exhausting server resources and the Immich API rate limits. **Vulnerability:** The `/api/exif/[id]` endpoint lacked rate limiting, making it vulnerable to DoS attacks. An attacker could rapidly request EXIF metadata, exhausting server resources and the Immich API rate limits.
**Learning:** Even simple GET endpoints that fetch metadata from an upstream server (like Immich) must be protected with rate limiting to prevent downstream DoS and resource exhaustion, especially when they perform backend API requests per hit. **Learning:** Even simple GET endpoints that fetch metadata from an upstream server (like Immich) must be protected with rate limiting to prevent downstream DoS and resource exhaustion, especially when they perform backend API requests per hit.
**Prevention:** Always implement rate limiting on endpoints that proxy requests or query a backend API, utilizing the shared `checkRateLimit` utility. **Prevention:** Always implement rate limiting on endpoints that proxy requests or query a backend API, utilizing the shared `checkRateLimit` utility.
## 2024-03-20 - [DoS via Upstream Healthcheck Abuse]
**Vulnerability:** The `/api/health` endpoint lacked downstream protection and made a live upstream request to the Immich server (`immich.ping()`) on every hit. An attacker could exhaust backend resources by spamming the health check endpoint.
**Learning:** Returning HTTP 429 (rate limiting) on a health check endpoint is an architectural anti-pattern that breaks load balancers and container orchestrators (causing self-inflicted downtime).
**Prevention:** To secure health checks that rely on upstream resources without breaking infrastructure polling, implement a short-lived in-memory cache (e.g., 10 seconds) for the upstream response rather than blocking the request entirely.

View File

@@ -10,8 +10,29 @@ import { immich } from '@/lib/immich';
const startTime = Date.now(); const startTime = Date.now();
// Cache the health check result for 10 seconds to prevent
// DoS attacks against the upstream Immich server while still
// allowing frequent polling from load balancers/orchestrators.
let cachedImmichOkPromise: Promise<boolean> | null = null;
let lastCheckTime = 0;
const CACHE_DURATION_MS = 10_000;
export async function GET() { export async function GET() {
const immichOk = await immich.ping(); const now = Date.now();
if (!cachedImmichOkPromise || now - lastCheckTime > CACHE_DURATION_MS) {
cachedImmichOkPromise = immich.ping();
lastCheckTime = now;
}
// Await the potentially shared promise to prevent a thundering herd
// of concurrent requests from hitting the upstream server on a cache miss.
let immichOk = false;
try {
immichOk = await cachedImmichOkPromise;
} catch {
cachedImmichOkPromise = null; // reset on error so next request retries
immichOk = false;
}
const body = { const body = {
status: immichOk ? 'ok' : 'degraded', status: immichOk ? 'ok' : 'degraded',