fix(audit): rate-limit/DoS — M13 (bulk limiter on 6 routes), M14 (api limiter default in withAuth, fail-open), M15 (export-pdf payload bounds); L21 verified not-a-bug
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,15 @@ export function deepMerge(
|
||||
export function withAuth<TParams extends RouteParams = Record<string, string>>(
|
||||
handler: RouteHandler<TParams>,
|
||||
): (req: NextRequest, routeContext: { params: Promise<TParams> }) => Promise<NextResponse> {
|
||||
// M14: apply the broad per-user `api` limiter (120/min) as a default
|
||||
// backstop for EVERY authenticated v1 request. Tighter named limiters
|
||||
// (`ai`, `bulk`, `ocr`, …) still compose ON TOP via `withRateLimit`
|
||||
// inside the handler chain - they use distinct Redis key prefixes, so
|
||||
// a request that trips a named limiter is counted in its own bucket
|
||||
// AND this `api` bucket independently (no double-counting within a
|
||||
// single bucket). `checkRateLimit` fails OPEN on a Redis outage
|
||||
// (see rate-limit.ts), so this can never lock the API out.
|
||||
const rateLimited = withRateLimit('api', handler as RouteHandler) as RouteHandler<TParams>;
|
||||
return async (req, routeContext) => {
|
||||
// Mint or accept a request id BEFORE entering the ALS frame so every
|
||||
// log line + the response header reference the same value. Clients
|
||||
@@ -269,7 +278,10 @@ export function withAuth<TParams extends RouteParams = Record<string, string>>(
|
||||
};
|
||||
|
||||
const params = await routeContext.params;
|
||||
return tag(await handler(req, ctx, params));
|
||||
// Call through the `api`-limited wrapper (M14). On a 429 it
|
||||
// short-circuits before the inner handler; otherwise it
|
||||
// delegates straight to the original handler.
|
||||
return tag(await rateLimited(req, ctx, params));
|
||||
} catch (error) {
|
||||
return tag(errorResponse(error));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user