diff --git a/CHANGELOG.md b/CHANGELOG.md index ab41734..26d9071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,19 @@ +## [0.13.3] - 2025-11-25 + +### 🐛 Bug Fixes + +- Add public host validation to prevent private host usage +- Format host validation error message for clarity and update dependencies +- Clippy suggestion ## [0.13.2] - 2025-11-04 ### 🐛 Bug Fixes - Update Redis dependency to version 0.32 and improve error handling in async item retrieval + +### ⚙️ Miscellaneous Tasks + +- Release ## [0.13.1] - 2025-11-04 ### 🐛 Bug Fixes diff --git a/Cargo.lock b/Cargo.lock index c03ef27..7276cd7 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", @@ -422,9 +431,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 +441,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 +469,37 @@ 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 = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + [[package]] name = "cobs" version = "0.3.0" @@ -715,9 +755,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", @@ -898,7 +938,7 @@ dependencies = [ [[package]] name = "fastedge-run" -version = "0.13.2" +version = "0.13.3" dependencies = [ "anyhow", "async-trait", @@ -1431,9 +1471,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 +1495,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.3" dependencies = [ "anyhow", "claims", @@ -1508,7 +1547,7 @@ dependencies = [ [[package]] name = "http-service" -version = "0.13.2" +version = "0.13.3" dependencies = [ "anyhow", "async-trait", @@ -1558,9 +1597,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 +1636,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", @@ -1755,7 +1794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -1865,7 +1904,7 @@ dependencies = [ [[package]] name = "key-value-store" -version = "0.13.2" +version = "0.13.3" dependencies = [ "async-trait", "reactor", @@ -2686,7 +2725,7 @@ dependencies = [ [[package]] name = "reactor" -version = "0.13.2" +version = "0.13.3" dependencies = [ "wasmtime", ] @@ -2803,7 +2842,7 @@ dependencies = [ [[package]] name = "runtime" -version = "0.13.2" +version = "0.13.3" dependencies = [ "anyhow", "async-trait", @@ -2980,7 +3019,7 @@ checksum = "0cd08a21f852bd2fe42e3b2a6c76a0db6a95a5b5bd29c0521dd0b30fa1712ec8" [[package]] name = "secret" -version = "0.13.2" +version = "0.13.3" dependencies = [ "anyhow", "reactor", @@ -3142,9 +3181,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", ] @@ -3887,12 +3926,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 +3973,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 +4352,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 +4926,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/Cargo.toml b/Cargo.toml index 615a25f..6f93eba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["crates/*"] resolver = "2" [workspace.package] -version = "0.13.2" +version = "0.13.3" edition = "2021" publish = false authors = ["FastEdge Development Team"] 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,