{"id":"24c6f61f75b6cc60","repo":"hyperium/hyper","slug":"invalid-chunk-extension-contains-newline","errorCode":null,"errorMessage":"invalid chunk extension contains newline","messagePattern":"invalid chunk extension contains newline","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src/proto/h1/decode.rs","lineNumber":437,"sourceCode":"                \"Invalid chunk size linear white space\",\n            ))),\n        }\n    }\n    fn read_extension<R: MemRead>(\n        cx: &mut Context<'_>,\n        rdr: &mut R,\n        extensions_cnt: &mut u64,\n    ) -> Poll<Result<ChunkedState, io::Error>> {\n        trace!(\"read_extension\");\n        // We don't care about extensions really at all. Just ignore them.\n        // They \"end\" at the next CRLF.\n        //\n        // However, some implementations may not check for the CR, so to save\n        // them from themselves, we reject extensions containing plain LF as\n        // well.\n        match byte!(rdr, cx) {\n            b'\\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),\n            b'\\n' => Poll::Ready(Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                \"invalid chunk extension contains newline\",\n            ))),\n            _ => {\n                *extensions_cnt += 1;\n                if *extensions_cnt >= CHUNKED_EXTENSIONS_LIMIT {\n                    Poll::Ready(Err(io::Error::new(\n                        io::ErrorKind::InvalidData,\n                        \"chunk extensions over limit\",\n                    )))\n                } else {\n                    Poll::Ready(Ok(ChunkedState::Extension))\n                }\n            } // no supported extensions\n        }\n    }\n    fn read_size_lf<R: MemRead>(\n        cx: &mut Context<'_>,","sourceCodeStart":419,"sourceCodeEnd":455,"githubUrl":"https://github.com/hyperium/hyper/blob/084473f728f9d07b3be5845475aa2f62ed9ff579/src/proto/h1/decode.rs#L419-L455","documentation":"Thrown in the Extension state (src/proto/h1/decode.rs:437) when a chunk extension contains a bare line-feed ('\\n') not preceded by '\\r'. hyper ignores chunk extension contents but defensively rejects a lone LF (which some broken senders use instead of CRLF) to avoid smuggling ambiguity. Reported as io::ErrorKind::InvalidData.","triggerScenarios":"A chunk extension that uses bare LF as a separator, e.g. a chunk line \"1;reject\\nnewlines\\r\\n\" where the extension text contains '\\n' before the terminating CRLF.","commonSituations":"A peer that normalizes CRLF to LF in the body; a logging/trace value embedded in a chunk extension that contains newlines; request smuggling attempts that exploit LF handling.","solutions":["Ensure chunk extensions are terminated by CRLF (\\r\\n), never a lone LF.","Strip CR/LF from any dynamic value placed into a chunk extension before writing it.","If you do not need chunk extensions, omit the ';' entirely."],"exampleFix":"// before: extension value contains a newline\nlet trace = \"a\\nb\";\nwrite!(w, \"1;t={}\\r\\nX\\r\\n\", trace).await?; // -> error 23\n\n// after: sanitize extension bytes\nlet trace = trace.replace(['\\r', '\\n'], \"_\");\nwrite!(w, \"1;t={}\\r\\nX\\r\\n\", trace).await?;","handlingStrategy":"validation","validationCode":"// Before writing a chunk extension, strip CR/LF so no bare LF can reach the wire.\nfn safe_ext(value: &str) -> String {\n    value.chars().map(|c| match c {\n        '\\r' | '\\n' => '_',\n        c => c,\n    }).collect()\n}\nwrite!(w, \"{:x};t={}\\r\\n\", len, safe_ext(&trace_id)).await?;","typeGuard":"fn has_no_newline(s: &str) -> bool { !s.as_bytes().iter().any(|b| matches!(b, b'\\r' | b'\\n')) }","tryCatchPattern":"Some(Err(e)) => {\n    let kind = e.source()\n        .and_then(|s| s.downcast_ref::<std::io::Error>())\n        .map(|io| io.kind());\n    if matches!(kind, Some(std::io::ErrorKind::InvalidData))\n        && e.to_string().contains(\"newline\") {\n        tracing::warn!(error=%e, \"peer sent LF inside chunk extension; possible smuggling\");\n        break;\n    }\n    return Err(e.into());\n}","preventionTips":["Sanitize any dynamic value placed in a chunk extension (strip CR/LF/NUL).","Treat a bare-LF-in-extension as a security signal: log and rate-limit the source.","Prefer trailers over chunk extensions for structured metadata."],"tags":["http","http1","chunked","extension","security","hyper","rust"],"analyzedSha":"084473f728f9d07b3be5845475aa2f62ed9ff579","analyzedAt":"2026-08-06T01:20:18.522Z","schemaVersion":2}