denoland/deno · error · TypeError

Multipart part has too many headers

Error message

Multipart part has too many headers

What it means

A hardening limit inside MultipartParser.#parseHeaders (ext/fetch/21_formdata.js). Each header-looking line (one containing ':') increments a counter, and exceeding MAX_MULTIPART_PART_HEADER_COUNT = 128 (line 392) throws this TypeError. The limit exists so a hostile or runaway multipart body cannot make the parser build an unbounded Headers object.

Source

Thrown at ext/fetch/21_formdata.js:435

  }

  /**
   * @param {string} headersText
   * @returns {{ headers: Headers, disposition: Map<string, string> }}
   */
  #parseHeaders(headersText) {
    const headers = new Headers();
    const rawHeaders = StringPrototypeSplit(headersText, "\r\n");
    let headerCount = 0;
    for (let i = 0; i < rawHeaders.length; ++i) {
      const rawHeader = rawHeaders[i];
      const sepIndex = StringPrototypeIndexOf(rawHeader, ":");
      if (sepIndex < 0) {
        continue; // Skip this header
      }
      headerCount++;
      if (headerCount > MAX_MULTIPART_PART_HEADER_COUNT) {
        throw new TypeError("Multipart part has too many headers");
      }
      const key = StringPrototypeSlice(rawHeader, 0, sepIndex);
      const value = StringPrototypeSlice(rawHeader, sepIndex + 1);
      headers.set(key, value);
    }

    const disposition = parseContentDisposition(
      headers.get("Content-Disposition") ?? "",
    );

    return { headers, disposition };
  }

  /**
   * @param {number} index
   * @returns {0 | 1 | 2}
   */
  #delimiterType(index) {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Fix the producer: cap and de-duplicate part headers well below 128 (well-behaved parts need ~5)
  2. Move per-part metadata into form fields rather than custom headers
  3. Treat the throw as a 400 when parsing untrusted requests: wrap formData() in try/catch and reject

Example fix

// before (sender)
for (const [k, v] of Object.entries(meta)) partHeaders.set(k, v)); // can exceed 128

// after (sender)
for (const [k, v] of Object.entries(meta)) form.set(`meta_${k}`, String(v))); // fields, not headers
Defensive patterns

Strategy: try-catch

Validate before calling

// sender side: keep part headers far below the 128 limit
const MAX_PART_HEADERS = 100;
if (Object.keys(partHeaders).length > MAX_PART_HEADERS) {
  throw new Error("too many part headers; move metadata into form fields");
}

Try / catch

try { return await req.formData(); } catch (e) {
  if (e instanceof TypeError && e.message.includes("too many headers")) {
    return new Response("malformed multipart part", { status: 400 });
  }
  throw e;
}

Prevention

When it happens

Trigger: A single multipart part whose header block contains more than 128 lines with ':' characters; a generator loop that appends the same header repeatedly; malformed bodies where a missing \r\n\r\n terminator causes data lines to be counted as headers.

Common situations: Custom multipart writers that duplicate headers per part; security/fuzz testing corpora; feeds that stuff metadata into part headers instead of the body.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/070a6b88b76386be. Report an issue: GitHub.