Skip to content

multipart: fileSizeLimit is validated after full buffering, so it does not bound memory (DoS) #35

Description

@pacocartones

Summary

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.byteLength
  if (totalSize > payloadLimit) {  }   // multipart passes payloadLimit = Number.POSITIVE_INFINITY
  else body.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, after Buffer.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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions