{"record":{"id":"d06e38a0440d0d84","repo":"Hmbown/CodeWhale","slug":"invalid-content-length","errorCode":null,"errorMessage":"invalid Content-Length","messagePattern":"invalid Content-Length","errorType":"http","errorClass":"FormBodyError","httpStatus":400,"severity":"warning","filePath":"web/lib/bounded-form.ts","lineNumber":22,"sourceCode":"    message: string\n  ) {\n    super(message);\n    this.name = \"FormBodyError\";\n  }\n}\n\nexport async function readBoundedUrlEncodedForm(\n  request: Request,\n  maxBytes: number\n): Promise<URLSearchParams> {\n  const mediaType = request.headers.get(\"content-type\")?.split(\";\", 1)[0]?.trim().toLowerCase();\n  if (mediaType !== \"application/x-www-form-urlencoded\") {\n    throw new FormBodyError(415, \"expected application/x-www-form-urlencoded\");\n  }\n\n  const rawLength = request.headers.get(\"content-length\");\n  if (rawLength !== null) {\n    if (!/^\\d+$/.test(rawLength)) throw new FormBodyError(400, \"invalid Content-Length\");\n    if (Number(rawLength) > maxBytes) throw new FormBodyError(413, \"payload too large\");\n  }\n\n  if (!request.body) return new URLSearchParams();\n\n  const reader = request.body.getReader();\n  const chunks: Uint8Array[] = [];\n  let total = 0;\n  while (true) {\n    const { done, value } = await reader.read();\n    if (done) break;\n    total += value.byteLength;\n    if (total > maxBytes) {\n      try {\n        await reader.cancel(\"payload too large\");\n      } catch {\n        // A source cancellation error must not obscure the enforced size limit.\n      }","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/web/lib/bounded-form.ts#L4-L40","documentation":"FormBodyError with HTTP 400: a Content-Length header was present but did not match /^\\d+$/ - it contained a sign, whitespace, hex, or garbage. The check runs before any body read so the size limit decision never trusts a malformed value.","triggerScenarios":"A client, proxy, or middleware injecting a malformed Content-Length such as ' 123', '-1', '0x40', or '1e3'; test tooling overriding the header by hand.","commonSituations":"Devtools/Postman manual overrides; a middleware recomputing the header from a float; hand-rolled HTTP clients that format numbers badly.","solutions":["Remove the custom Content-Length and let the HTTP stack compute it","Fix middleware/proxy logic that rewrites the header","Replay the request without the header to confirm it was the cause"],"exampleFix":"// before\nfetch(url, { method: 'POST', headers: { 'content-length': String(payload.length * 2) }, body: payload });\n\n// after - omit content-length; the client computes it\nfetch(url, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: payload });","handlingStrategy":"validation","validationCode":"const len = headers.get('content-length');\nif (len !== null && !/^\\d+$/.test(len)) {\n  headers.delete('content-length'); // let the transport recompute it\n}","typeGuard":null,"tryCatchPattern":"try {\n  const params = await readBoundedUrlEncodedForm(request, MAX_BYTES);\n} catch (err) {\n  if (err instanceof FormBodyError && err.status === 400) {\n    return new Response('malformed request headers', { status: 400 });\n  }\n  throw err;\n}","preventionTips":["Never hand-set Content-Length in fetch/axios calls","Audit proxies and middleware that rewrite hop-by-hop headers","Treat non-digit Content-Length as a client bug, not something to coerce"],"tags":["web","http","headers","validation"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}