{"record":{"id":"9be666332f468dc6","repo":"can1357/oh-my-pi","slug":"invalid-content-length","errorCode":null,"errorMessage":"invalid content-length","messagePattern":"invalid content-length","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"python/robomp/src/proxy/server.py","lineNumber":423,"sourceCode":"        return f\"{request.url.path}?{query}\" if query else request.url.path\n\n    async def _read_body_capped(request: Request) -> bytes:\n        \"\"\"Read the request body with a hard byte cap.\n\n        Checks `Content-Length` first (cheap reject before any read), then\n        streams chunks via `request.stream()` with a running counter so a\n        client that lies about (or omits) the header still can't get more\n        than `max_bytes` into memory. We deliberately do NOT call\n        `request.body()` first — that would buffer the full payload before\n        auth checks ever run.\n        \"\"\"\n        max_bytes = settings.gh_proxy_max_body_bytes\n        cl = request.headers.get(\"content-length\")\n        if cl is not None:\n            try:\n                declared = int(cl)\n            except ValueError as exc:\n                raise HTTPException(400, \"invalid content-length\") from exc\n            if declared > max_bytes:\n                raise HTTPException(status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, \"request body too large\")\n        chunks: list[bytes] = []\n        total = 0\n        async for chunk in request.stream():\n            if not chunk:\n                continue\n            total += len(chunk)\n            if total > max_bytes:\n                raise HTTPException(status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, \"request body too large\")\n            chunks.append(chunk)\n        body = b\"\".join(chunks)\n        # Starlette's `request.body()` / `request.json()` re-read from\n        # `request._body`. We consumed the stream above, so seed the cache\n        # to keep downstream JSON parsing working without a second read.\n        request._body = body  # type: ignore[attr-defined]\n        return body\n","sourceCodeStart":405,"sourceCodeEnd":441,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/proxy/server.py#L405-L441","documentation":"_read_body_capped reads request bodies with a size cap. If a Content-Length header is present but not a valid integer (int(cl) raises ValueError), the proxy returns HTTP 400 'invalid content-length'. This protects downstream size enforcement from silently ignoring a malformed header.","triggerScenarios":"Sending a request to an authenticated gh-proxy endpoint with a Content-Length header that is not a decimal integer, e.g. 'Content-Length: abc', '10, 20', or a header containing units like '10 bytes'.","commonSituations":"Misconfigured proxies/clients emitting duplicate or malformed Content-Length; hand-rolled HTTP clients writing the header manually; fuzzing or non-conformant middleware rewriting headers.","solutions":["Fix the client to send a valid decimal Content-Length (or omit it and use chunked transfer-encoding)","Inspect intermediate proxies/CDNs that may rewrite the header and fix their configuration","Test with curl or a standard HTTP library, which always emits a correct Content-Length for fixed bodies"],"exampleFix":"// before\nheaders: { \"Content-Length\": String(body.length) + \" bytes\" }\n// after\nheaders: { \"Content-Length\": String(body.length) }","handlingStrategy":"validation","validationCode":"const cl = headers[\"content-length\"];\nif (cl !== undefined && !/^\\d+$/.test(cl)) throw new Error(`invalid content-length: ${cl}`);","typeGuard":"function isValidContentLength(v: unknown): v is string {\n  return typeof v === \"string\" && /^\\d+$/.test(v);\n}","tryCatchPattern":null,"preventionTips":["Let your HTTP client set Content-Length automatically","Audit middleware/proxies that rewrite or duplicate headers","Use standard HTTP libraries rather than hand-rolled request writers"],"tags":["http-400","http-headers","content-length"],"backgroundTag":"malformed-http-header","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}