From 0d86c184aff8c867cf2f8edac4fd1d884aca55a8 Mon Sep 17 00:00:00 2001 From: Ruslan Pislari Date: Tue, 25 Nov 2025 11:40:37 +0200 Subject: [PATCH 1/4] fix: add public host validation to prevent private host usage --- crates/http-backend/src/lib.rs | 56 ++++++++++++++++++++++++++++++++ crates/http-service/src/state.rs | 7 ++++ 2 files changed, 63 insertions(+) diff --git a/crates/http-backend/src/lib.rs b/crates/http-backend/src/lib.rs index e187749..89a9df6 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}; @@ -219,6 +220,13 @@ impl Backend { 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 +496,54 @@ impl hyper_util::client::legacy::connect::Connection for Connection { } } +pub fn is_public_host(host: &str) -> bool { + // 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 +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 +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::*; 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, From 63796cfba23b1cfe900af6c48ec458ca8f68ca0a Mon Sep 17 00:00:00 2001 From: Ruslan Pislari Date: Tue, 25 Nov 2025 16:03:53 +0200 Subject: [PATCH 2/4] fix: add public host validation and extract host function; downgrade package versions to 0.13.0-2 --- Cargo.lock | 271 ++++++++++++++++++++--------- crates/http-backend/src/lib.rs | 308 +++++++++++++++++++++++++++++++++ 2 files changed, 499 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c03ef27..8cd40cb 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]] @@ -195,6 +195,15 @@ dependencies = [ "cfg_aliases", ] +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -232,15 +241,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 +387,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", @@ -414,6 +423,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cityhash-rs" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a719913643003b84bd13022b4b7e703c09342cd03b679c4641c7d2e50dc34d" + [[package]] name = "claims" version = "0.8.0" @@ -422,9 +437,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 +447,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", @@ -460,6 +475,43 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "clickhouse" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a9a81a1dffadd762ee662635ce409232258ce9beebd7cc0fa227df0b5e7efc0" +dependencies = [ + "bstr", + "bytes", + "cityhash-rs", + "clickhouse-derive", + "futures", + "futures-channel", + "http-body-util", + "hyper", + "hyper-util", + "lz4_flex", + "replace_with", + "sealed", + "serde", + "static_assertions", + "thiserror 1.0.69", + "tokio", + "url", +] + +[[package]] +name = "clickhouse-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d70f3e2893f7d3e017eeacdc9a708fbc29a10488e3ebca21f9df6a5d2b616dbb" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + [[package]] name = "cobs" version = "0.3.0" @@ -715,9 +767,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", @@ -743,6 +795,14 @@ dependencies = [ "syn", ] +[[package]] +name = "dictionary" +version = "0.13.0-2" +dependencies = [ + "async-trait", + "reactor", +] + [[package]] name = "digest" version = "0.10.7" @@ -898,12 +958,13 @@ dependencies = [ [[package]] name = "fastedge-run" -version = "0.13.2" +version = "0.13.0-2" dependencies = [ "anyhow", "async-trait", "bytesize", "clap", + "dictionary", "http", "http-backend", "http-body-util", @@ -919,7 +980,6 @@ dependencies = [ "smol_str", "tempfile", "tokio", - "utils", "wasmtime", ] @@ -942,9 +1002,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 +1403,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 +1491,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,18 +1515,17 @@ 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", ] [[package]] name = "http-backend" -version = "0.13.2" +version = "0.13.0-2" dependencies = [ "anyhow", "claims", @@ -1508,12 +1567,14 @@ dependencies = [ [[package]] name = "http-service" -version = "0.13.2" +version = "0.13.0-2" dependencies = [ "anyhow", "async-trait", "bytes", + "bytesize", "claims", + "dictionary", "http", "http-backend", "http-body-util", @@ -1530,7 +1591,6 @@ dependencies = [ "tokio", "tracing", "tracing-test", - "utils", "wasi-common", "wasmtime", "wasmtime-wasi", @@ -1558,9 +1618,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 +1657,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", @@ -1610,7 +1670,7 @@ dependencies = [ "hyper", "libc", "pin-project-lite", - "socket2", + "socket2 0.6.1", "tokio", "tower-service", "tracing", @@ -1750,12 +1810,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", ] @@ -1808,6 +1868,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -1865,14 +1934,13 @@ dependencies = [ [[package]] name = "key-value-store" -version = "0.13.2" +version = "0.13.0-2" dependencies = [ "async-trait", "reactor", "redis", "slab", "smol_str", - "tokio", "tracing", "wasmtime", ] @@ -1960,6 +2028,12 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +[[package]] +name = "lz4_flex" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" + [[package]] name = "mach2" version = "0.4.3" @@ -2686,7 +2760,7 @@ dependencies = [ [[package]] name = "reactor" -version = "0.13.2" +version = "0.13.0-2" dependencies = [ "wasmtime", ] @@ -2699,27 +2773,26 @@ checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" [[package]] name = "redis" -version = "0.32.7" +version = "0.27.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "014cc767fefab6a3e798ca45112bccad9c6e0e218fbd49720042716c73cfef44" +checksum = "09d8f99a4090c89cc489a94833c901ead69bfbf3877b4867d5482e321ee875bc" dependencies = [ "arc-swap", + "async-trait", "backon", "bytes", - "cfg-if", "combine", - "futures-channel", + "futures", "futures-util", + "itertools 0.13.0", "itoa", - "native-tls", "num-bigint", "percent-encoding", "pin-project-lite", "ryu", "sha1_smol", - "socket2", + "socket2 0.5.10", "tokio", - "tokio-native-tls", "tokio-util", "url", ] @@ -2787,6 +2860,12 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "replace_with" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51743d3e274e2b18df81c4dc6caf8a5b8e15dbe799e0dca05c7617380094e884" + [[package]] name = "ring" version = "0.17.14" @@ -2803,7 +2882,7 @@ dependencies = [ [[package]] name = "runtime" -version = "0.13.2" +version = "0.13.0-2" dependencies = [ "anyhow", "async-trait", @@ -2811,6 +2890,8 @@ dependencies = [ "bytesize", "chrono", "claims", + "clickhouse", + "dictionary", "http", "http-backend", "key-value-store", @@ -2824,7 +2905,6 @@ dependencies = [ "tokio", "tokio-util", "tracing", - "utils", "wasi-common", "wasmtime", "wasmtime-environ 36.0.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0)", @@ -2978,9 +3058,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cd08a21f852bd2fe42e3b2a6c76a0db6a95a5b5bd29c0521dd0b30fa1712ec8" +[[package]] +name = "sealed" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "secret" -version = "0.13.2" +version = "0.13.0-2" dependencies = [ "anyhow", "reactor", @@ -3056,6 +3147,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_json" version = "1.0.145" @@ -3142,9 +3244,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", ] @@ -3174,6 +3276,16 @@ dependencies = [ "serde_core", ] +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.1" @@ -3190,6 +3302,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -3204,9 +3322,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", @@ -3406,7 +3524,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.1", "tokio-macros", "windows-sys 0.61.2", ] @@ -3726,13 +3844,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "utils" -version = "0.13.2" -dependencies = [ - "reactor", -] - [[package]] name = "uuid" version = "1.18.1" @@ -3887,12 +3998,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 +4045,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 +4424,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 +4998,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 89a9df6..0249ff6 100644 --- a/crates/http-backend/src/lib.rs +++ b/crates/http-backend/src/lib.rs @@ -217,6 +217,7 @@ impl Backend { None } }); + let original_host = original_host .or_else(|| request_host_header.clone()) .unwrap_or_default(); @@ -496,7 +497,21 @@ 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), @@ -828,4 +843,297 @@ 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()); + } + + } From 24e6dd23ea7bc68acd320be36be03379d2aa73fd Mon Sep 17 00:00:00 2001 From: Ruslan Pislari Date: Mon, 1 Dec 2025 10:40:13 +0200 Subject: [PATCH 3/4] fix: update private IP address checks --- Cargo.lock | 166 ++++++--------------------------- crates/http-backend/src/lib.rs | 2 + 2 files changed, 29 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8cd40cb..833fe3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,15 +195,6 @@ dependencies = [ "cfg_aliases", ] -[[package]] -name = "bstr" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" -dependencies = [ - "memchr", -] - [[package]] name = "bumpalo" version = "3.19.0" @@ -423,12 +414,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "cityhash-rs" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a719913643003b84bd13022b4b7e703c09342cd03b679c4641c7d2e50dc34d" - [[package]] name = "claims" version = "0.8.0" @@ -475,43 +460,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" -[[package]] -name = "clickhouse" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9a81a1dffadd762ee662635ce409232258ce9beebd7cc0fa227df0b5e7efc0" -dependencies = [ - "bstr", - "bytes", - "cityhash-rs", - "clickhouse-derive", - "futures", - "futures-channel", - "http-body-util", - "hyper", - "hyper-util", - "lz4_flex", - "replace_with", - "sealed", - "serde", - "static_assertions", - "thiserror 1.0.69", - "tokio", - "url", -] - -[[package]] -name = "clickhouse-derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70f3e2893f7d3e017eeacdc9a708fbc29a10488e3ebca21f9df6a5d2b616dbb" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn", -] - [[package]] name = "cobs" version = "0.3.0" @@ -795,14 +743,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dictionary" -version = "0.13.0-2" -dependencies = [ - "async-trait", - "reactor", -] - [[package]] name = "digest" version = "0.10.7" @@ -958,13 +898,12 @@ dependencies = [ [[package]] name = "fastedge-run" -version = "0.13.0-2" +version = "0.13.2" dependencies = [ "anyhow", "async-trait", "bytesize", "clap", - "dictionary", "http", "http-backend", "http-body-util", @@ -980,6 +919,7 @@ dependencies = [ "smol_str", "tempfile", "tokio", + "utils", "wasmtime", ] @@ -1525,7 +1465,7 @@ dependencies = [ [[package]] name = "http-backend" -version = "0.13.0-2" +version = "0.13.2" dependencies = [ "anyhow", "claims", @@ -1567,14 +1507,12 @@ dependencies = [ [[package]] name = "http-service" -version = "0.13.0-2" +version = "0.13.2" dependencies = [ "anyhow", "async-trait", "bytes", - "bytesize", "claims", - "dictionary", "http", "http-backend", "http-body-util", @@ -1591,6 +1529,7 @@ dependencies = [ "tokio", "tracing", "tracing-test", + "utils", "wasi-common", "wasmtime", "wasmtime-wasi", @@ -1670,7 +1609,7 @@ dependencies = [ "hyper", "libc", "pin-project-lite", - "socket2 0.6.1", + "socket2", "tokio", "tower-service", "tracing", @@ -1868,15 +1807,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -1934,13 +1864,14 @@ dependencies = [ [[package]] name = "key-value-store" -version = "0.13.0-2" +version = "0.13.2" dependencies = [ "async-trait", "reactor", "redis", "slab", "smol_str", + "tokio", "tracing", "wasmtime", ] @@ -2028,12 +1959,6 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -[[package]] -name = "lz4_flex" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" - [[package]] name = "mach2" version = "0.4.3" @@ -2760,7 +2685,7 @@ dependencies = [ [[package]] name = "reactor" -version = "0.13.0-2" +version = "0.13.2" dependencies = [ "wasmtime", ] @@ -2773,26 +2698,27 @@ checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" [[package]] name = "redis" -version = "0.27.6" +version = "0.32.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d8f99a4090c89cc489a94833c901ead69bfbf3877b4867d5482e321ee875bc" +checksum = "014cc767fefab6a3e798ca45112bccad9c6e0e218fbd49720042716c73cfef44" dependencies = [ "arc-swap", - "async-trait", "backon", "bytes", + "cfg-if", "combine", - "futures", + "futures-channel", "futures-util", - "itertools 0.13.0", "itoa", + "native-tls", "num-bigint", "percent-encoding", "pin-project-lite", "ryu", "sha1_smol", - "socket2 0.5.10", + "socket2", "tokio", + "tokio-native-tls", "tokio-util", "url", ] @@ -2860,12 +2786,6 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" -[[package]] -name = "replace_with" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51743d3e274e2b18df81c4dc6caf8a5b8e15dbe799e0dca05c7617380094e884" - [[package]] name = "ring" version = "0.17.14" @@ -2882,7 +2802,7 @@ dependencies = [ [[package]] name = "runtime" -version = "0.13.0-2" +version = "0.13.2" dependencies = [ "anyhow", "async-trait", @@ -2890,8 +2810,6 @@ dependencies = [ "bytesize", "chrono", "claims", - "clickhouse", - "dictionary", "http", "http-backend", "key-value-store", @@ -2905,6 +2823,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "utils", "wasi-common", "wasmtime", "wasmtime-environ 36.0.2 (git+https://github.com/G-Core/wasmtime.git?branch=release-36.0.0)", @@ -3058,20 +2977,9 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cd08a21f852bd2fe42e3b2a6c76a0db6a95a5b5bd29c0521dd0b30fa1712ec8" -[[package]] -name = "sealed" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "secret" -version = "0.13.0-2" +version = "0.13.2" dependencies = [ "anyhow", "reactor", @@ -3147,17 +3055,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "serde_json" version = "1.0.145" @@ -3276,16 +3173,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.1" @@ -3302,12 +3189,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" @@ -3524,7 +3405,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -3844,6 +3725,13 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "utils" +version = "0.13.2" +dependencies = [ + "reactor", +] + [[package]] name = "uuid" version = "1.18.1" diff --git a/crates/http-backend/src/lib.rs b/crates/http-backend/src/lib.rs index 0249ff6..22b2510 100644 --- a/crates/http-backend/src/lib.rs +++ b/crates/http-backend/src/lib.rs @@ -527,6 +527,7 @@ fn is_private_ip(ip: &IpAddr) -> bool { } /// 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() @@ -541,6 +542,7 @@ fn is_private_ipv4(ip: &Ipv4Addr) -> bool { } /// 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() From 1456b6352d7305d24add2f982884bb1c20a92ca8 Mon Sep 17 00:00:00 2001 From: Ruslan Pislari Date: Mon, 1 Dec 2025 13:38:22 +0200 Subject: [PATCH 4/4] fix: improve readability of error assertions in request tests --- crates/http-backend/src/lib.rs | 52 ++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/crates/http-backend/src/lib.rs b/crates/http-backend/src/lib.rs index 22b2510..9963161 100644 --- a/crates/http-backend/src/lib.rs +++ b/crates/http-backend/src/lib.rs @@ -943,7 +943,10 @@ mod tests { // IPv6 with port assert_eq!(extract_host("[::1]:8080"), "::1"); - assert_eq!(extract_host("[2001:4860:4860::8888]:443"), "2001:4860:4860::8888"); + assert_eq!( + extract_host("[2001:4860:4860::8888]:443"), + "2001:4860:4860::8888" + ); // IPv6 without port assert_eq!(extract_host("[::1]"), "::1"); @@ -969,7 +972,10 @@ mod tests { }; let result = backend.make_request(req); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("private host not allowed")); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); // Test private network let req = Request { @@ -980,7 +986,10 @@ mod tests { }; let result = backend.make_request(req); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("private host not allowed")); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); // Test another private network let req = Request { @@ -991,7 +1000,10 @@ mod tests { }; let result = backend.make_request(req); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("private host not allowed")); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); // Test link-local let req = Request { @@ -1002,7 +1014,10 @@ mod tests { }; let result = backend.make_request(req); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("private host not allowed")); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); } #[test] @@ -1020,7 +1035,10 @@ mod tests { }; let result = backend.make_request(req); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("private host not allowed")); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); // Test with Host header containing private IP with port let req = Request { @@ -1031,7 +1049,10 @@ mod tests { }; let result = backend.make_request(req); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("private host not allowed")); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); } #[test] @@ -1049,7 +1070,10 @@ mod tests { }; let result = backend.make_request(req); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("private host not allowed")); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); // Test unique local let req = Request { @@ -1060,7 +1084,10 @@ mod tests { }; let result = backend.make_request(req); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("private host not allowed")); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); // Test link-local let req = Request { @@ -1071,7 +1098,10 @@ mod tests { }; let result = backend.make_request(req); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("private host not allowed")); + assert!(result + .unwrap_err() + .to_string() + .contains("private host not allowed")); } #[test] @@ -1136,6 +1166,4 @@ mod tests { let result = backend.make_request(req); assert!(result.is_ok()); } - - }