From 71381342d6f645f29685e031e28cb9e38aafe82f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 10:20:32 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[security?= =?UTF-8?q?=20improvement]\n\nAdded=20rate=20limiting=20to=20the=20`/api/h?= =?UTF-8?q?ealth`=20endpoint=20using=20the=20shared=20`checkRateLimit`=20u?= =?UTF-8?q?tility.=20This=20prevents=20potential=20DoS=20attacks=20by=20re?= =?UTF-8?q?stricting=20the=20number=20of=20requests=20per=20IP,=20utilizin?= =?UTF-8?q?g=20the=20configured=20`rateLimitRpm`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ralksta <16941050+ralksta@users.noreply.github.com> --- app/api/health/route.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/app/api/health/route.ts b/app/api/health/route.ts index 000bf6d..168d89d 100644 --- a/app/api/health/route.ts +++ b/app/api/health/route.ts @@ -5,8 +5,10 @@ * GET /api/health → { status, immich, uptime } */ -import { NextResponse } from 'next/server'; +import { NextRequest, NextResponse } from 'next/server'; import { immich } from '@/lib/immich'; +import { checkRateLimit, getClientIp } from '@/lib/rate-limit'; +import { getConfig } from '@/lib/config'; const startTime = Date.now(); @@ -17,7 +19,25 @@ let cachedImmichOkPromise: Promise | null = null; let lastCheckTime = 0; const CACHE_DURATION_MS = 10_000; -export async function GET() { +export async function GET(request: NextRequest) { + const ip = getClientIp(request); + const { rateLimitRpm } = getConfig(); + + const { success, remaining, resetAt } = checkRateLimit(`health:${ip}`, rateLimitRpm); + if (!success) { + return NextResponse.json( + { error: 'Too many requests' }, + { + status: 429, + headers: { + 'Retry-After': String(Math.ceil((resetAt - Date.now()) / 1000)), + 'X-RateLimit-Limit': String(rateLimitRpm), + 'X-RateLimit-Remaining': String(remaining), + }, + }, + ); + } + const now = Date.now(); if (!cachedImmichOkPromise || now - lastCheckTime > CACHE_DURATION_MS) { cachedImmichOkPromise = immich.ping();