Complete security hardening: zero-migration foundation, edge headers, rate-limit atomics, body limits
Local Build and Deploy / deploy (push) Successful in 58s
Local Build and Deploy / deploy (push) Successful in 58s
- Make @/lib/safe-action re-export from foundation layer so all 13+ existing server actions instantly get request tracing, rate limiting, and structured error handling without any code changes - Add HSTS, CSP, X-Frame-Options, X-Content-Type-Options to edge proxy (src/proxy.ts) — ran at Cloudflare/Vercel edge for all non-asset routes - Fix rate-limit.ts race condition: compute newCount before assignment to shrink the read-modify-write window; add memory-key prefix to avoid collisions with Redis keys - Add request body size limit (10 MB default) to api-handler.ts with per-route override via maxBodyBytes option - Remove unused imports and clean up backward-compat types
This commit is contained in:
1 parent
f6ad030c5b
commit
e2fc7ea1a4
6 files changed
+55
-178
No files matched your search
+10
-1
@@ -6,6 +6,7 @@ import { logServerError } from "@/lib/server-log";
|
||||
import { validateCsrfToken } from "@/lib/foundation/security";
|
||||
|
||||
const MUTATING_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
||||
const MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB
|
||||
|
||||
type AdminContext = NonNullable<Awaited<ReturnType<typeof getApiAdminContext>>>;
|
||||
type RouteContext = { params?: Promise<Record<string, string | string[]>> };
|
||||
@@ -15,7 +16,7 @@ type AdminHandler = (
|
||||
routeContext: RouteContext,
|
||||
) => Promise<Response> | Response;
|
||||
|
||||
export function withAdmin(options: { permission?: string; requireCsrf?: boolean }, handler: AdminHandler) {
|
||||
export function withAdmin(options: { permission?: string; requireCsrf?: boolean; maxBodyBytes?: number }, handler: AdminHandler) {
|
||||
return async (request: NextRequest, routeContext: RouteContext = {}) => {
|
||||
if (options.requireCsrf === true && MUTATING_METHODS.has(request.method)) {
|
||||
const csrfToken = request.headers.get("x-csrf-token") ?? request.headers.get("csrf-token") ?? "";
|
||||
@@ -25,6 +26,14 @@ export function withAdmin(options: { permission?: string; requireCsrf?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
if (MUTATING_METHODS.has(request.method)) {
|
||||
const contentLength = request.headers.get("content-length");
|
||||
const maxBytes = options.maxBodyBytes ?? MAX_BODY_BYTES;
|
||||
if (contentLength && Number(contentLength) > maxBytes) {
|
||||
return NextResponse.json({ ok: false, error: `Request body exceeds ${maxBytes} bytes` }, { status: 413 });
|
||||
}
|
||||
}
|
||||
|
||||
const context = await getApiAdminContext();
|
||||
if (!context) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
|
||||
if (
|
||||
|
||||
Reference in new issue
Block a user