{"record":{"id":"5d2219a9b7bf6a7d","repo":"BigPizzaV3/CodexPlusPlus","slug":"error-5d2219","errorCode":null,"errorMessage":"解压后的请求体超过大小限制","messagePattern":"解压后的请求体超过大小限制","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/launcher.rs","lineNumber":1299,"sourceCode":"    }\n    stream.shutdown().await?;\n    Ok(())\n}\n\nfn decode_protocol_proxy_request_body(\n    body: &[u8],\n    content_encoding: Option<&str>,\n) -> anyhow::Result<String> {\n    let encoding = content_encoding.unwrap_or_default().trim();\n    let decoded = if encoding.is_empty() || encoding.eq_ignore_ascii_case(\"identity\") {\n        body.to_vec()\n    } else if encoding.eq_ignore_ascii_case(\"zstd\") {\n        let decoder = zstd::stream::read::Decoder::new(std::io::Cursor::new(body))?;\n        let mut limited = decoder.take((MAX_HTTP_BODY_BYTES + 1) as u64);\n        let mut decoded = Vec::new();\n        limited.read_to_end(&mut decoded)?;\n        if decoded.len() > MAX_HTTP_BODY_BYTES {\n            anyhow::bail!(\"解压后的请求体超过大小限制\");\n        }\n        decoded\n    } else {\n        anyhow::bail!(\"不支持的 Content-Encoding：{encoding}\");\n    };\n\n    String::from_utf8(decoded)\n        .map_err(|error| anyhow::anyhow!(\"Responses 请求体不是 UTF-8：{error}\"))\n}\n\nfn overlay_image_response() -> (String, Vec<u8>, String, &'static str) {\n    let not_found = || {\n        (\n            \"404 Not Found\".to_string(),\n            serde_json::to_vec(&serde_json::json!({\n                \"status\": \"failed\",\n                \"message\": \"图片覆盖层未启用或图片不可用\"\n            }))","sourceCodeStart":1281,"sourceCodeEnd":1317,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/launcher.rs#L1281-L1317","documentation":"decode_protocol_proxy_request_body (crates/codex-plus-core/src/launcher.rs:1286) decodes Responses-API request bodies forwarded through the local protocol proxy. For Content-Encoding: zstd it streams through a take(MAX_HTTP_BODY_BYTES + 1) reader and rejects anything whose decompressed length exceeds MAX_HTTP_BODY_BYTES (32 MiB, defined at launcher.rs:1874). This is a decompression-bomb guard: a small compressed body that inflates past 32 MiB is refused before it is ever parsed.","triggerScenarios":"A client sends a request to the local proxy's Responses endpoint with Content-Encoding: zstd where the inflated payload exceeds 32 MiB — e.g. enormous conversation state, huge base64 attachments inlined into the request, or a malicious/buggy upstream sending a zstd bomb.","commonSituations":"Very long Codex sessions whose serialized Responses payload grows past 32 MiB; clients that inline images/base64 blobs into request bodies; a proxy or middleware re-encoding bodies with zstd at high ratios; adversarial traffic against the loopback listener.","solutions":["Shrink the request: remove inlined base64/attachment payloads from the Responses body or split the session so the serialized request stays under 32 MiB","If you control both ends and legitimately need larger bodies, raise MAX_HTTP_BODY_BYTES in crates/codex-plus-core/src/launcher.rs:1874 and add/adjust the tests at launcher.rs:3211+ that pin the limit","Send the body uncompressed (drop Content-Encoding: zstd) — the raw path still enforces the 32 MiB ceiling but avoids the decode step misreporting","If unexpected, inspect what is actually inflating: capture the compressed size vs decompressed size to distinguish bloat from an attack"],"exampleFix":"// before: client inlines a giant artifact\nlet body = serde_json::to_vec(&request_with_40mb_base64)?; // inflates past limit\nproxy_post(\"/responses\", zstd_compress(body), \"zstd\").await?; // bails\n\n// after: reference instead of inline\nrequest.artifact_url = upload_artifact(&blob).await?; // small body\nlet body = serde_json::to_vec(&request)?;\nproxy_post(\"/responses\", zstd_compress(body), \"zstd\").await?;","handlingStrategy":"validation","validationCode":"// Client side: check compressed AND estimated decompressed size before sending\nlet compressed = zstd_compress(&body)?;\nif body.len() > 32 * 1024 * 1024 {\n    anyhow::bail!(\"body would exceed proxy limit ({} bytes); trim payload\", body.len());\n}","typeGuard":"fn within_proxy_limit(decompressed_len: usize) -> bool {\n    decompressed_len <= 32 * 1024 * 1024 // MAX_HTTP_BODY_BYTES\n}","tryCatchPattern":"match decode_protocol_proxy_request_body(&body, encoding).await {\n    Err(e) if e.to_string().contains(\"解压后的请求体超过大小限制\") => {\n        respond_413_payload_too_large().await // map to 413 for the caller\n    }\n    rest => rest,\n}","preventionTips":["Keep Responses bodies under 32 MiB by referencing large artifacts instead of inlining them","Monitor session payload growth for long conversations; start a new session when serialized size climbs","If raising MAX_HTTP_BODY_BYTES, update the pinned tests at launcher.rs:3211+ in the same change"],"tags":["rust","http-proxy","zstd","payload-limit","decompression-bomb","responses-api"],"backgroundTag":"payload-too-large","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}