feat: maxRequestBodySize server option#216
Conversation
The `readBody` fallback used by `NodeRequest.text()`/`.json()` (when the body is not consumed as a stream) accumulated all chunks with no size limit, so a large/malicious body could grow memory unbounded. Add an opt-in `maxBodySize` (bytes) `ServerOptions` field. When set, the buffered fallback tracks accumulated length and, once exceeded, pauses the stream and rejects with a 413-style error (`statusCode`/`status: 413`, `code: "ERR_BODY_TOO_LARGE"`). Default is unchanged (no limit), so this is non-breaking. Streaming (`request.body`) is unaffected. Co-Authored-By: Claude Fable 5 <[email protected]>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (15)
👮 Files not reviewed due to content moderation or server errors (15)
📝 Walkthrough🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/adapters/_node/request.ts (1)
212-220: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winConsider a
Content-Lengthfast path to avoid reading any body bytes for obviously oversized requests.Checking
req.headers["content-length"]againstmaxBodySizebefore attachingdatalisteners would reject clearly-oversized requests immediately, without consuming any socket data.♻️ Suggested addition
function readBody(req: NodeServerRequest, maxBodySize?: number): Promise<any> { // https://github.com/GoogleCloudPlatform/functions-framework-nodejs/... if ("rawBody" in req && Buffer.isBuffer(req.rawBody)) { if (maxBodySize !== undefined && req.rawBody.length > maxBodySize) { return Promise.reject(createBodyTooLargeError(maxBodySize)); } return Promise.resolve(req.rawBody); } + + if (maxBodySize !== undefined) { + const contentLength = Number(req.headers["content-length"]); + if (Number.isFinite(contentLength) && contentLength > maxBodySize) { + return Promise.reject(createBodyTooLargeError(maxBodySize)); + } + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/adapters/_node/request.ts` around lines 212 - 220, The readBody helper currently waits to inspect the request body before rejecting oversized payloads, which misses an early Content-Length check. Update readBody in the Node request adapter to inspect req.headers["content-length"] before attaching any data listeners and immediately reject via createBodyTooLargeError(maxBodySize) when the declared size is obviously too large; keep the existing rawBody handling and fallback streaming behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/adapters/_node/request.ts`:
- Around line 229-242: In onData within request body handling, oversized uploads
should not call req.pause() because it can leave the socket tied up; instead,
change the maxBodySize overflow path to either keep draining and discarding the
remaining body or mark the connection for closure and destroy the socket after
responding with 413. Update the cleanup/error path around
createBodyTooLargeError so the request is fully consumed or closed instead of
merely paused.
---
Nitpick comments:
In `@src/adapters/_node/request.ts`:
- Around line 212-220: The readBody helper currently waits to inspect the
request body before rejecting oversized payloads, which misses an early
Content-Length check. Update readBody in the Node request adapter to inspect
req.headers["content-length"] before attaching any data listeners and
immediately reject via createBodyTooLargeError(maxBodySize) when the declared
size is obviously too large; keep the existing rawBody handling and fallback
streaming behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fcb0ed68-7870-4d97-ba89-fd10048ca8f8
📒 Files selected for processing (4)
src/adapters/_node/request.tssrc/adapters/node.tssrc/types.tstest/node-max-body-size.test.ts
Constructing NodeRequest reused the passed context object for
`runtime.node` before maxBodySize was added; the maxBodySize change
switched it to a fresh `{ req, res }` literal to keep maxBodySize off
the public shape, adding a ~4-5 ns allocation to every request on the
hot path. Reuse `ctx` directly again: maxBodySize may ride along but is
intentionally not part of the public `runtime.node` type, and consumers
only read req/res. Restores main's single-allocation profile (measured
~25 ns/ctor, identical to main).
Co-Authored-By: Claude Fable 5 <[email protected]>
Follow-up: hot-path allocation fix + Bun/Deno default comparisonPerf fix (pushed): The initial commit changed Do other runtimes impose a default?
So there's precedent (Bun) for a safe default, but Deno and raw Node have none. Left as |
The guard previously only covered the buffered readBody() fallback used
by text()/json(). Reads that route through the native Request —
arrayBuffer(), blob(), bytes(), formData() — and direct streaming of
request.body bypassed it entirely, leaving the largest/most common heavy
path (multipart uploads via formData) uncapped.
Enforce the limit at the single choke point every consumer funnels
through: wrap request.body's ReadableStream with a pull-based limiter
that counts bytes and errors with the same 413-style error once the
limit is exceeded, cancelling the upstream Node stream. This covers the
native Request methods and streaming while preserving backpressure. The
readBody() fast path keeps its own cheap check. When maxBodySize is
undefined (default) the body stream is not wrapped, so the hot path is
unchanged.
Also thread maxBodySize through toNodeHandler(handler, { maxBodySize })
so handlers embedded in an existing Node server can opt in.
Co-Authored-By: Claude Fable 5 <[email protected]>
Update:
|
maxBodySize was Node-only. Make it a generic server option enforced on every runtime that srvx can enforce it on: - Extract the body-limit primitives into a runtime-agnostic module (src/_body-limit.ts): createBodyTooLargeError, limitBodyStream (used by the Node body getter) and limitRequestBody (rebuilds a native Request with a size-limited body, preserving method/url/headers/signal). - Bun: map maxBodySize to Bun's native `maxRequestBodySize` (Bun returns 413 before the handler runs); an explicit `bun.maxRequestBodySize` still wins. - Deno: Deno.serve has no native option, so enforce in the fetch wrapper by rebuilding the request with a limited body stream. No-op when unset or bodyless, so the default hot path is unchanged. Verified end-to-end on all three runtimes (413 over the limit, 200 within) with automated tests: Node (vitest), plus Bun/Deno via spawned fixtures. Co-Authored-By: Claude Fable 5 <[email protected]>
Update:
|
Match Bun's native `maxRequestBodySize` option name (which the Bun adapter forwards to directly) and be explicit that the limit applies to the request body. The option is unreleased, so no alias is kept. Co-Authored-By: Claude Fable 5 <[email protected]>
Co-Authored-By: Claude Fable 5 <[email protected]>
maxRequestBodySize server option
Same as srvx-fast but with maxRequestBodySize set high enough to never trigger, to track the fast-path enforcement overhead. Overhead is within run-to-run noise (the per-chunk check is a length add + compare, dwarfed by socket I/O + JSON in the end-to-end path). Co-Authored-By: Claude Fable 5 <[email protected]>
Adds an opt-in
maxRequestBodySizeserver option that caps the size of an incoming request body, enforced across Node, Bun, and Deno.Motivation
srvx had no way to bound request-body memory. The buffered read path (
request.text()/request.json()) accumulated every chunk with no limit, and the streamed/native paths (arrayBuffer(),blob(),bytes(),formData(),request.body) had none either — so a large or malicious upload (multipartformData()being the most common heavy case) could grow process memory unbounded. Bun ships a nativemaxRequestBodySize(default 128 MiB); Node and Deno have no default limit.What this does
New generic
ServerOptions.maxRequestBodySize(bytes, defaultundefined= no limit). When a body exceeds it, reading aborts and rejects with a413-style error (statusCode: 413,status: 413,code: "ERR_BODY_TOO_LARGE") that a handler oronErrorcan map to413 Payload Too Large.The limit covers all read paths — buffered (
text()/json()) and streamed (request.body, and thereforearrayBuffer()/blob()/bytes()/formData()).Per-runtime enforcement
request.body'sReadableStreamwith a pull-based byte-limiter (preserves backpressure, cancels upstream on overflow); the bufferedreadBody()fast path keeps its own cheap check.maxRequestBodySize— Bun responds413before the handler runs. An explicitbun.maxRequestBodySizestill wins.Deno.servehas no native option, so srvx rebuilds the request with a size-limited body stream (method/url/headers/signal preserved).The shared limiter primitives live in a runtime-agnostic
src/_body-limit.ts. Also threaded throughtoNodeHandler(handler, { maxRequestBodySize })for handlers embedded in an existing Node server.Hot path / backward compat
Default
undefined→ no limit, identical to today. When unset, no body-stream wrapping and no per-request allocation occurs on any runtime (a separate commit also reverted an incidental per-request object allocation so the Node default path matchesmainexactly, ~25 ns/ctor).Tests
test/node-max-body-size.test.ts— 9 cases: per-method (text/json/arrayBuffer/bytes/blob/formData/streaming) over-limit → 413, within-limit → 200, and no-limit-by-default backward-compat.test/bun-max-body-size.test.ts+test/deno-max-body-size.test.ts— spawn the target runtime againsttest/fixtures/max-body-server.tsand assert 413 over / 200 within.All suites green (node/bun/deno adapter suites + body-size tests: 191 passed, 8 skipped).
pnpm lintandpnpm typecheckclean. Docs updated indocs/1.guide/5.options.md.Notes for review
ERR_BODY_TOO_LARGEas a catchable rejection. Inherent to using Bun's native option.Requestper bodied request when a limit is set (opt-in only) — a small allocation on that path; happy to gate it further if preferred.maxRequestBodySizefor consistency; unreleased, so no alias kept.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes