Problem
Nothing caps request body size. toWebRequest builds an unbounded ReadableStream and the framework-owned action RPC endpoint plus route readBody buffer an arbitrarily large POST entirely into memory before the handler runs (invokeAction does await req.text() at actions.js:299/411, route readBody at json.js:58), with no content-length ceiling and no 413 path. An unbounded POST to /__webjs/action/* (reachable by any client holding a CSRF token) or any route.js handler is a trivial memory-exhaustion DoS. The same ingress is also unbounded in time: createHttp1Server + server.listen set no requestTimeout, headersTimeout, or keepAliveTimeout and no per-request deadline, so a slowloris slow-header client or a hung handler holds a connection indefinitely (the only timeouts in the codebase are vendor-fetch and SSE keepalive).
Design / approach
Count bytes off the native request ReadableStream and abort with 413 past a default cap (no full buffering), and set node:http's built-in requestTimeout/headersTimeout/keepAliveTimeout at listen time. Both are native knobs, not a new middleware layer.
Web-standards fit: Uses the native Streams API for the size check and node:http's own timeout properties; the framework adds only a counter and a few listen-time assignments.
Prior art: Express/body-parser 100kb default, Next.js api bodyParser.sizeLimit (1mb), Remix parseMultipartFormData maxPartSize, Rails ActionDispatch request-size protections.
Acceptance criteria
Filed from the production-readiness audit (webjs vs Next.js / Remix / Rails / Turbo / Lit). Theme: runtime. Priority: P1. Kept to webjs identity: no-build, progressive enhancement, web-components-first, AI-first, batteries-included, close to web standards.
Problem
Nothing caps request body size. toWebRequest builds an unbounded ReadableStream and the framework-owned action RPC endpoint plus route readBody buffer an arbitrarily large POST entirely into memory before the handler runs (invokeAction does await req.text() at actions.js:299/411, route readBody at json.js:58), with no content-length ceiling and no 413 path. An unbounded POST to /__webjs/action/* (reachable by any client holding a CSRF token) or any route.js handler is a trivial memory-exhaustion DoS. The same ingress is also unbounded in time: createHttp1Server + server.listen set no requestTimeout, headersTimeout, or keepAliveTimeout and no per-request deadline, so a slowloris slow-header client or a hung handler holds a connection indefinitely (the only timeouts in the codebase are vendor-fetch and SSE keepalive).
Design / approach
Count bytes off the native request ReadableStream and abort with 413 past a default cap (no full buffering), and set node:http's built-in requestTimeout/headersTimeout/keepAliveTimeout at listen time. Both are native knobs, not a new middleware layer.
Web-standards fit: Uses the native Streams API for the size check and node:http's own timeout properties; the framework adds only a counter and a few listen-time assignments.
Prior art: Express/body-parser 100kb default, Next.js api bodyParser.sizeLimit (1mb), Remix parseMultipartFormData maxPartSize, Rails ActionDispatch request-size protections.
Acceptance criteria
Filed from the production-readiness audit (webjs vs Next.js / Remix / Rails / Turbo / Lit). Theme: runtime. Priority: P1. Kept to webjs identity: no-build, progressive enhancement, web-components-first, AI-first, batteries-included, close to web standards.