{"record":{"id":"070a6b88b76386be","repo":"denoland/deno","slug":"multipart-part-has-too-many-headers","errorCode":null,"errorMessage":"Multipart part has too many headers","messagePattern":"Multipart part has too many headers","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/fetch/21_formdata.js","lineNumber":435,"sourceCode":"  }\n\n  /**\n   * @param {string} headersText\n   * @returns {{ headers: Headers, disposition: Map<string, string> }}\n   */\n  #parseHeaders(headersText) {\n    const headers = new Headers();\n    const rawHeaders = StringPrototypeSplit(headersText, \"\\r\\n\");\n    let headerCount = 0;\n    for (let i = 0; i < rawHeaders.length; ++i) {\n      const rawHeader = rawHeaders[i];\n      const sepIndex = StringPrototypeIndexOf(rawHeader, \":\");\n      if (sepIndex < 0) {\n        continue; // Skip this header\n      }\n      headerCount++;\n      if (headerCount > MAX_MULTIPART_PART_HEADER_COUNT) {\n        throw new TypeError(\"Multipart part has too many headers\");\n      }\n      const key = StringPrototypeSlice(rawHeader, 0, sepIndex);\n      const value = StringPrototypeSlice(rawHeader, sepIndex + 1);\n      headers.set(key, value);\n    }\n\n    const disposition = parseContentDisposition(\n      headers.get(\"Content-Disposition\") ?? \"\",\n    );\n\n    return { headers, disposition };\n  }\n\n  /**\n   * @param {number} index\n   * @returns {0 | 1 | 2}\n   */\n  #delimiterType(index) {","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/fetch/21_formdata.js#L417-L453","documentation":"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.","triggerScenarios":"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.","commonSituations":"Custom multipart writers that duplicate headers per part; security/fuzz testing corpora; feeds that stuff metadata into part headers instead of the body.","solutions":["Fix the producer: cap and de-duplicate part headers well below 128 (well-behaved parts need ~5)","Move per-part metadata into form fields rather than custom headers","Treat the throw as a 400 when parsing untrusted requests: wrap formData() in try/catch and reject"],"exampleFix":"// before (sender)\nfor (const [k, v] of Object.entries(meta)) partHeaders.set(k, v)); // can exceed 128\n\n// after (sender)\nfor (const [k, v] of Object.entries(meta)) form.set(`meta_${k}`, String(v))); // fields, not headers","handlingStrategy":"try-catch","validationCode":"// sender side: keep part headers far below the 128 limit\nconst MAX_PART_HEADERS = 100;\nif (Object.keys(partHeaders).length > MAX_PART_HEADERS) {\n  throw new Error(\"too many part headers; move metadata into form fields\");\n}","typeGuard":null,"tryCatchPattern":"try { return await req.formData(); } catch (e) {\n  if (e instanceof TypeError && e.message.includes(\"too many headers\")) {\n    return new Response(\"malformed multipart part\", { status: 400 });\n  }\n  throw e;\n}","preventionTips":["Cap and de-duplicate part headers at the producer","Put per-part metadata in form fields, not custom headers","Treat multipart parser TypeErrors from untrusted bodies as 400s, not 500s"],"tags":["fetch","form-data","multipart","limits"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}