diff --git a/Cargo.lock b/Cargo.lock index c03ef27..833fe3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,22 +73,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -232,15 +232,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "bytesize" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c99fa31e08a43eaa5913ef68d7e01c37a2bdce6ed648168239ad33b7d30a9cd8" +checksum = "00f4369ba008f82b968b1acbe31715ec37bd45236fa0726605a36cc3060ea256" [[package]] name = "candle-core" @@ -378,9 +378,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.45" +version = "1.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", "jobserver", @@ -422,9 +422,9 @@ checksum = "bba18ee93d577a8428902687bcc2b6b45a56b1981a1f6d779731c86cc4c5db18" [[package]] name = "clap" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -432,9 +432,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -715,9 +715,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -942,9 +942,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fnv" @@ -1343,9 +1343,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1431,9 +1431,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -1455,12 +1455,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -1558,9 +1557,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -1597,9 +1596,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "bytes", "futures-channel", @@ -1750,12 +1749,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -3142,9 +3141,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] @@ -3204,9 +3203,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.110" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -3887,12 +3886,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.240.0" +version = "0.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d642d8c5ecc083aafe9ceb32809276a304547a3a6eeecceb5d8152598bc71f" +checksum = "e01164c9dda68301e34fdae536c23ed6fe90ce6d97213ccc171eebbd3d02d6b8" dependencies = [ "leb128fmt", - "wasmparser 0.240.0", + "wasmparser 0.241.2", ] [[package]] @@ -3934,9 +3933,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.240.0" +version = "0.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b722dcf61e0ea47440b53ff83ccb5df8efec57a69d150e4f24882e4eba7e24a4" +checksum = "46d90019b1afd4b808c263e428de644f3003691f243387d30d673211ee0cb8e8" dependencies = [ "bitflags 2.10.0", "indexmap", @@ -4313,24 +4312,24 @@ dependencies = [ [[package]] name = "wast" -version = "240.0.0" +version = "241.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0efe1c93db4ac562b9733e3dca19ed7fc878dba29aef22245acf84f13da4a19" +checksum = "63f66e07e2ddf531fef6344dbf94d112df7c2f23ed6ffb10962e711500b8d816" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.240.0", + "wasm-encoder 0.241.2", ] [[package]] name = "wat" -version = "1.240.0" +version = "1.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec9b6eab7ecd4d639d78515e9ea491c9bacf494aa5eda10823bd35992cf8c1e" +checksum = "45f923705c40830af909c5dec2352ec2821202e4a66008194585e1917458a26d" dependencies = [ - "wast 240.0.0", + "wast 241.0.2", ] [[package]] @@ -4887,18 +4886,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", diff --git a/crates/http-backend/src/lib.rs b/crates/http-backend/src/lib.rs index e187749..9963161 100644 --- a/crates/http-backend/src/lib.rs +++ b/crates/http-backend/src/lib.rs @@ -2,6 +2,7 @@ pub mod stats; use std::fmt::Debug; use std::future::Future; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; @@ -216,9 +217,17 @@ impl Backend { None } }); + let original_host = original_host .or_else(|| request_host_header.clone()) .unwrap_or_default(); + + anyhow::ensure!( + is_public_host(&original_host), + "private host not allowed: {}", + original_host + ); + // filter headers let mut headers = req .headers @@ -488,6 +497,70 @@ impl hyper_util::client::legacy::connect::Connection for Connection { } } +fn extract_host(addr: &str) -> &str { + if addr.starts_with('[') { + // IPv6 with port: [::1]:8080 + addr.split(']') + .next() + .unwrap_or(addr) + .trim_start_matches('[') + } else { + // IPv4 with port or just host + addr.rsplit_once(':').map(|(host, _)| host).unwrap_or(addr) + } +} + +pub fn is_public_host(host: &str) -> bool { + let host = extract_host(host); + // Try to parse as IP address + match host.parse::() { + Ok(ip) => !is_private_ip(&ip), + Err(_) => true, // Not an IP address, assume it's a hostname + } +} + +fn is_private_ip(ip: &IpAddr) -> bool { + match ip { + IpAddr::V4(ipv4) => is_private_ipv4(ipv4), + IpAddr::V6(ipv6) => is_private_ipv6(ipv6), + } +} + +/// Check if an IPv4 address is private +/// This is a a copu of std::net::Ipv4Addr::is_global with inverted logic and some additions +fn is_private_ipv4(ip: &Ipv4Addr) -> bool { + ip.octets()[0] == 0 // "This network" + || ip.is_private() + || ip.is_loopback() + || ip.is_link_local() + || ( + ip.octets()[0] == 192 && ip.octets()[1] == 0 && ip.octets()[2] == 0 + && ip.octets()[3] != 9 && ip.octets()[3] != 10 + ) + || ip.is_documentation() + || ip.is_broadcast() +} + +/// Check if an IPv6 address is private +/// This is a a copu of std::net::Ipv6Addr::is_global with inverted logic and some additions +fn is_private_ipv6(ip: &Ipv6Addr) -> bool { + ip.is_unspecified() + || ip.is_loopback() + || matches!(ip.segments(), [0, 0, 0, 0, 0, 0xffff, _, _]) + || matches!(ip.segments(), [0x64, 0xff9b, 1, _, _, _, _, _]) + || matches!(ip.segments(), [0x100, 0, 0, 0, _, _, _, _]) + || (matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200) + && !(u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001 + || u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002 + || matches!(ip.segments(), [0x2001, 3, _, _, _, _, _, _]) + || matches!(ip.segments(), [0x2001, 4, 0x112, _, _, _, _, _]) + || matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if (0x20..=0x3F).contains(&b)))) + || matches!(ip.segments(), [0x2002, _, _, _, _, _, _, _]) + || matches!(ip.segments(), [0x5f00, ..]) + || ip.is_unique_local() + || ip.is_unicast_link_local() +} + #[cfg(test)] mod tests { use super::*; @@ -772,4 +845,325 @@ mod tests { let error = claims::assert_err!(backend.send_request(req).await); assert_eq!("too-many-requests", error.name()); } + + #[test] + fn test_is_public_host_private_ipv4() { + // Loopback + assert!(!is_public_host("127.0.0.1")); + assert!(!is_public_host("127.0.0.1:8080")); + + // Private networks + assert!(!is_public_host("10.0.0.1")); + assert!(!is_public_host("10.255.255.255:3000")); + assert!(!is_public_host("172.16.0.1")); + assert!(!is_public_host("172.31.255.254:8080")); + assert!(!is_public_host("192.168.1.1")); + assert!(!is_public_host("192.168.0.1:9000")); + + // Link-local + assert!(!is_public_host("169.254.0.1")); + assert!(!is_public_host("169.254.169.254:80")); + + // Broadcast + assert!(!is_public_host("255.255.255.255")); + + // This network (0.0.0.0/8) + assert!(!is_public_host("0.0.0.0")); + assert!(!is_public_host("0.1.2.3:8080")); + + // Documentation addresses + assert!(!is_public_host("192.0.2.1")); + assert!(!is_public_host("198.51.100.1")); + assert!(!is_public_host("203.0.113.1")); + } + + #[test] + fn test_is_public_host_public_ipv4() { + // Public IP addresses + assert!(is_public_host("8.8.8.8")); + assert!(is_public_host("8.8.8.8:53")); + assert!(is_public_host("1.1.1.1")); + assert!(is_public_host("1.1.1.1:443")); + assert!(is_public_host("93.184.216.34")); + assert!(is_public_host("93.184.216.34:80")); + } + + #[test] + fn test_is_public_host_private_ipv6() { + // Loopback + assert!(!is_public_host("[::1]")); + assert!(!is_public_host("[::1]:8080")); + + // Unspecified + assert!(!is_public_host("[::]")); + assert!(!is_public_host("[::]:8080")); + + // Link-local + assert!(!is_public_host("[fe80::1]")); + assert!(!is_public_host("[fe80::1]:8080")); + + // Unique local + assert!(!is_public_host("[fc00::1]")); + assert!(!is_public_host("[fd00::1]")); + assert!(!is_public_host("[fd00::1]:9000")); + + // IPv4-mapped IPv6 + assert!(!is_public_host("[::ffff:127.0.0.1]")); + assert!(!is_public_host("[::ffff:192.168.1.1]")); + } + + #[test] + fn test_is_public_host_public_ipv6() { + // Public IPv6 addresses + assert!(is_public_host("[2001:4860:4860::8888]")); + assert!(is_public_host("[2001:4860:4860::8888]:443")); + assert!(is_public_host("[2606:4700:4700::1111]")); + assert!(is_public_host("[2606:4700:4700::1111]:80")); + } + + #[test] + fn test_is_public_host_domain_names() { + // Domain names should be considered public + assert!(is_public_host("example.com")); + assert!(is_public_host("example.com:8080")); + assert!(is_public_host("www.example.com")); + assert!(is_public_host("api.example.com:443")); + assert!(is_public_host("subdomain.example.co.uk")); + assert!(is_public_host("localhost")); // hostname, not IP + } + + #[test] + fn test_extract_host() { + // IPv4 with port + assert_eq!(extract_host("127.0.0.1:8080"), "127.0.0.1"); + assert_eq!(extract_host("192.168.1.1:3000"), "192.168.1.1"); + + // IPv4 without port + assert_eq!(extract_host("127.0.0.1"), "127.0.0.1"); + + // IPv6 with port + assert_eq!(extract_host("[::1]:8080"), "::1"); + assert_eq!( + extract_host("[2001:4860:4860::8888]:443"), + "2001:4860:4860::8888" + ); + + // IPv6 without port + assert_eq!(extract_host("[::1]"), "::1"); + + // Domain names + assert_eq!(extract_host("example.com:8080"), "example.com"); + assert_eq!(extract_host("example.com"), "example.com"); + assert_eq!(extract_host("sub.example.com:443"), "sub.example.com"); + } + + #[test] + fn test_make_request_rejects_private_ipv4() { + let connector = mock_http_connector::Connector::builder().build(); + let backend = Backend::::builder(BackendStrategy::FastEdge) + .build(connector); + + // Test loopback + let req = Request { + method: Method::Get, + uri: "http://127.0.0.1/path".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); + + // Test private network + let req = Request { + method: Method::Get, + uri: "http://192.168.1.1:8080/path".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); + + // Test another private network + let req = Request { + method: Method::Get, + uri: "http://10.0.0.1/path".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); + + // Test link-local + let req = Request { + method: Method::Get, + uri: "http://169.254.169.254/metadata".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); + } + + #[test] + fn test_make_request_rejects_private_ipv4_from_host_header() { + let connector = mock_http_connector::Connector::builder().build(); + let backend = Backend::::builder(BackendStrategy::FastEdge) + .build(connector); + + // Test with Host header containing private IP + let req = Request { + method: Method::Get, + uri: "/path".to_string(), + headers: vec![("host".to_string(), "192.168.1.1".to_string())], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); + + // Test with Host header containing private IP with port + let req = Request { + method: Method::Get, + uri: "/path".to_string(), + headers: vec![("host".to_string(), "127.0.0.1:8080".to_string())], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); + } + + #[test] + fn test_make_request_rejects_private_ipv6() { + let connector = mock_http_connector::Connector::builder().build(); + let backend = Backend::::builder(BackendStrategy::FastEdge) + .build(connector); + + // Test loopback + let req = Request { + method: Method::Get, + uri: "http://[::1]/path".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); + + // Test unique local + let req = Request { + method: Method::Get, + uri: "http://[fc00::1]:8080/path".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); + + // Test link-local + let req = Request { + method: Method::Get, + uri: "http://[fe80::1]/path".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); + } + + #[test] + fn test_make_request_accepts_public_ip() { + let connector = mock_http_connector::Connector::builder().build(); + let backend = Backend::::builder(BackendStrategy::FastEdge) + .build(connector); + + // Test public IPv4 + let req = Request { + method: Method::Get, + uri: "http://8.8.8.8/path".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_ok()); + + // Test public IPv6 + let req = Request { + method: Method::Get, + uri: "http://[2001:4860:4860::8888]/path".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_ok()); + + // Test domain name + let req = Request { + method: Method::Get, + uri: "http://example.com/path".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_ok()); + } + + #[test] + fn test_make_request_direct_strategy_allows_private_ip() { + let connector = mock_http_connector::Connector::builder().build(); + let backend = Backend::::builder(BackendStrategy::Direct) + .build(connector); + + // Direct strategy should allow private IPs + let req = Request { + method: Method::Get, + uri: "http://127.0.0.1/path".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_ok()); + + let req = Request { + method: Method::Get, + uri: "http://192.168.1.1/path".to_string(), + headers: vec![], + body: None, + }; + let result = backend.make_request(req); + assert!(result.is_ok()); + } } diff --git a/crates/http-service/src/state.rs b/crates/http-service/src/state.rs index d5d8940..b15af1a 100644 --- a/crates/http-service/src/state.rs +++ b/crates/http-service/src/state.rs @@ -2,6 +2,7 @@ use anyhow::Error; use http::request::Parts; use http::uri::Scheme; use http::{header, HeaderMap, HeaderName, Uri}; +use http_backend::is_public_host; use http_backend::Backend; use runtime::store::HasStats; use runtime::util::stats::StatsVisitor; @@ -47,6 +48,12 @@ impl BackendRequest for HttpState { }) .unwrap_or_default(); + anyhow::ensure!( + is_public_host(&original_host), + "private host not allowed: {}", + original_host + ); + static FILTER_HEADERS: [HeaderName; 6] = [ header::HOST, header::CONTENT_LENGTH,