security: bound Zod inputs, add SSE per-user cap and tRPC body limit (#51, PR #59)
CI / Architecture Guardrails (push) Successful in 3m38s
CI / Assistant Split Regression (push) Successful in 4m40s
CI / Lint (push) Successful in 5m17s
CI / Typecheck (push) Successful in 5m46s
CI / Build (push) Successful in 7m1s
CI / Unit Tests (push) Failing after 9m41s
CI / Release Images (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / E2E Tests (push) Has started running
CI / Architecture Guardrails (push) Successful in 3m38s
CI / Assistant Split Regression (push) Successful in 4m40s
CI / Lint (push) Successful in 5m17s
CI / Typecheck (push) Successful in 5m46s
CI / Build (push) Successful in 7m1s
CI / Unit Tests (push) Failing after 9m41s
CI / Release Images (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / E2E Tests (push) Has started running
Closes #51 (ESLint rule + conventions doc remain as follow-up). Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com> Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
This commit was merged in pull request #59.
This commit is contained in:
@@ -17,6 +17,11 @@ function extractClientIp(req: NextRequest): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Hard cap on tRPC request body size to prevent memory/CPU amplification from
|
||||
// a single oversized payload. Stream uploads (files, reports) don't go through
|
||||
// tRPC. 2 MiB is comfortably above any legitimate tRPC batch call.
|
||||
const MAX_TRPC_BODY_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
// Throttle lastActiveAt updates: max once per 60s per user
|
||||
const lastActiveCache = new Map<string, number>();
|
||||
const ACTIVITY_THROTTLE_MS = 60_000;
|
||||
@@ -37,6 +42,23 @@ function trackActivity(userId: string) {
|
||||
}
|
||||
|
||||
const handler = async (req: NextRequest) => {
|
||||
// Reject oversized bodies before we touch auth, DB, or the router. A tRPC
|
||||
// mutation should never exceed MAX_TRPC_BODY_BYTES. Content-Length is
|
||||
// advisory — also guard against chunked requests below via length check
|
||||
// on the cloned body.
|
||||
if (req.method !== "GET") {
|
||||
const declaredLength = req.headers.get("content-length");
|
||||
if (declaredLength) {
|
||||
const parsed = Number(declaredLength);
|
||||
if (Number.isFinite(parsed) && parsed > MAX_TRPC_BODY_BYTES) {
|
||||
return new Response(JSON.stringify({ error: "Request body too large" }), {
|
||||
status: 413,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
|
||||
// Validate active session registry on every authenticated request.
|
||||
|
||||
Reference in New Issue
Block a user