You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
multipart: fileSizeLimit is validated after full buffering, so it does not bound memory (DoS) #35
fileSizeLimit does not bound memory for multipart bodies, because it is validated after the entire request body has already been buffered and concatenated. A single in-flight multipart/form-data request under the default limits can pin hundreds of MB of server memory.
Detail (at HEAD, v5.1.1)
The shared accumulator in p() pushes every chunk into an in-memory array and only guards against payloadLimit (src/index.ts):
req.on('data',(chunk)=>{totalSize+=chunk.byteLengthif(totalSize>payloadLimit){…}// multipart passes payloadLimit = Number.POSITIVE_INFINITYelsebody.push(chunk)// unbounded})req.on('end',()=>resolve(fn(Buffer.concat(body),req)))// full concat, then td.decode → whole body as a string
multipart() explicitly sets payloadLimit = Number.POSITIVE_INFINITY, so that guard never fires. The only multipart cap, fileSizeLimit (default 200 MiB), is checked inside parseMultipart, afterBuffer.concat + td.decode (src/index.ts ~line 159: if (data.length > fileSizeLimit) throw …). So it validates after the memory is already consumed — it is a post-hoc size check, not a memory guard.
Measured with a plain app.use(multipart()) server (Node 24): a 150 MiB body — under both default limits — returns 200 while server RSS grows ~+460 MiB and stays there; N concurrent such requests OOM the process. (400/600 MiB bodies do hit fileSizeLimit/V8 string-length, but only after buffering.)
Note: #22 added request limits to the other parsers but multipart kept Infinity, so this looks like an oversight rather than a deliberate choice.
Suggested fix
Enforce a finite cap during accumulation so it triggers before Buffer.concat. Minimal: default multipart's payloadLimit to a finite value (e.g. aligned with fileSizeLimit) so the existing totalSize > payloadLimit guard fires mid-stream. Happy to open a PR if that direction is acceptable.
Activity