Reject duplicate Host headers in the network proxy (#53506)

## Why

Policy evaluation and upstream parsers must not select different `Host` field lines from the same request.

## What changed

Return `400 Bad Request` for multiple `Host` headers, including identical values, before policy evaluation, upstream forwarding, or Unix socket routing. Apply the check to both plain HTTP requests and `CONNECT` requests.

## Testing

Add regression tests covering identical and conflicting headers in origin-form, absolute-form, and `CONNECT` requests, verifying rejection without an upstream connection. Also cover single-header forwarding and rejection before Unix socket routing.

GitOrigin-RevId: 8ec95171e120f784629d3121b9a1bedbaf88e7cd
This commit is contained in:
jfoo-oai
2026-10-12 23:46:16 +00:00
committed by copyberry
parent 92caad7828
commit 12cca49c81
2 changed files with 185 additions and 0 deletions
+17
View File
@@ -190,6 +190,12 @@ async fn http_connect_accept(
environment_id: Option<String>,
mut req: Request,
) -> Result<(Response, Request), Response> {
if req.headers().get_all(header::HOST).iter().nth(1).is_some() {
return Err(text_response(
StatusCode::BAD_REQUEST,
"multiple Host headers",
));
}
let started_at = Instant::now();
let app_state = req
.extensions()
@@ -559,6 +565,13 @@ async fn http_plain_proxy(
environment_id: Option<String>,
mut req: Request,
) -> Result<Response, Infallible> {
// Policy and upstream parsers must not select different Host field lines.
if req.headers().get_all(header::HOST).iter().nth(1).is_some() {
return Ok(text_response(
StatusCode::BAD_REQUEST,
"multiple Host headers",
));
}
let started_at = Instant::now();
let app_state = match req.extensions().get::<Arc<NetworkProxyState>>().cloned() {
Some(state) => state,
@@ -1261,6 +1274,10 @@ struct BlockedResponse<'a> {
message: Option<String>,
}
#[cfg(test)]
#[path = "http_proxy/host_header_tests.rs"]
mod host_header_tests;
#[cfg(test)]
mod tests {
use super::*;