{"record":{"id":"1c74d3c72aa9e99e","repo":"Hmbown/CodeWhale","slug":"invalid-content-length","errorCode":null,"errorMessage":"invalid Content-Length","messagePattern":"invalid Content-Length","errorType":"http","errorClass":"BodyReadError","httpStatus":400,"severity":"error","filePath":"web/lib/bounded-body.ts","lineNumber":12,"sourceCode":"export class BodyReadError extends Error {\n  constructor(readonly status: 400 | 413, message: string) {\n    super(message);\n    this.name = \"BodyReadError\";\n  }\n}\n\n/** Count raw bytes while reading; Content-Length is only an early rejection. */\nexport async function readBoundedBody(request: { body: ReadableStream<Uint8Array> | null; headers: Headers }, maxBytes: number): Promise<Uint8Array> {\n  const reject = async (status: 400 | 413, message: string): Promise<never> => {\n    try { await request.body?.cancel(message); } catch { /* Keep the original rejection. */ }\n    throw new BodyReadError(status, message);\n  };\n  const rawLength = request.headers.get(\"content-length\");\n  if (rawLength !== null) {\n    if (!/^\\d+$/.test(rawLength)) return reject(400, \"invalid Content-Length\");\n    if (Number(rawLength) > maxBytes) return reject(413, \"payload too large\");\n  }\n  if (!request.body) return new Uint8Array();\n\n  const reader = request.body.getReader();\n  const chunks: Uint8Array[] = [];\n  let total = 0;\n  try {\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      total += value.byteLength;\n      if (total > maxBytes) throw new BodyReadError(413, \"payload too large\");\n      chunks.push(value);","sourceCodeStart":1,"sourceCodeEnd":30,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/lib/bounded-body.ts#L1-L30","documentation":"BodyReadError with status 400 raised by readBoundedBody's reject() helper when the request's Content-Length header is missing or not a valid integer. Content-Length is used only as an early rejection signal; a malformed value means the HTTP request itself is malformed, so reading is aborted and the stream cancelled before any bytes are consumed.","triggerScenarios":"A client sends a Content-Length header that is not all digits — e.g. `\"abc\"`, negative values, or a header containing whitespace/units — to any route using readBoundedBody (such as the admin form POST).","commonSituations":"Hand-rolled HTTP clients or curl scripts with a malformed -H 'Content-Length: ...'; misbehaving proxies rewriting the header; fuzzed or hostile traffic (the check is intentional hardening).","solutions":["Fix the client to send a correct decimal byte count for Content-Length, or omit it and use chunked transfer encoding.","Remove stray characters/units from the manually set Content-Length header.","If behind a proxy, check whether the proxy mangles the header and correct its configuration."],"exampleFix":"// before\ncurl -H 'Content-Length: 12 bytes' -d 'token=x' https://host/api/admin/login\n// after\ncurl -d 'token=x' https://host/api/admin/login","handlingStrategy":"validation","validationCode":"const cl = request.headers.get(\"content-length\");\nif (cl !== null && !/^\\d+$/.test(cl)) throw new Error(\"client sent invalid Content-Length\");","typeGuard":null,"tryCatchPattern":"try {\n  const body = await readBoundedBody(request, MAX);\n} catch (err) {\n  if (err instanceof BodyReadError && err.status === 400 && err.message === \"invalid Content-Length\") {\n    return new Response(\"invalid Content-Length header\", { status: 400 });\n  }\n  throw err;\n}","preventionTips":["Let the HTTP client compute Content-Length automatically instead of setting it by hand.","Never include units, signs, or whitespace in Content-Length values.","Prefer chunked transfer encoding for streaming bodies with unknown length."],"tags":["http","validation","headers"],"backgroundTag":"invalid-argument-format","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}