From 8a304505dff2aab609a4efc3a84ea301d8fa401c Mon Sep 17 00:00:00 2001 From: fettpl <38704082+fettpl@users.noreply.github.com> Date: Sun, 15 Feb 2026 00:04:05 +0100 Subject: [PATCH] fix: apply TimeoutLayer to gateway router for request timeouts Add tower-http TimeoutLayer with the existing REQUEST_TIMEOUT_SECS (30s) constant and 408 Request Timeout status code. Previously, the constant was defined but no timeout middleware was applied, allowing slow requests to hold connections indefinitely (slow-loris risk). Closes #60 Co-Authored-By: Claude Opus 4.6 --- src/gateway/mod.rs | 10 +++++++--- src/security/secrets.rs | 2 +- src/tools/browser.rs | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index deba8ff..039139b 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -23,7 +23,9 @@ use axum::{ }; use std::net::SocketAddr; use std::sync::Arc; +use std::time::Duration; use tower_http::limit::RequestBodyLimitLayer; +use tower_http::timeout::TimeoutLayer; /// Maximum request body size (64KB) — prevents memory exhaustion pub const MAX_BODY_SIZE: usize = 65_536; @@ -163,8 +165,6 @@ pub async fn run_gateway(host: &str, port: u16, config: Config) -> Result<()> { }; // Build router with middleware - // Note: Body limit layer prevents memory exhaustion from oversized requests - // Timeout is handled by tokio's TcpListener accept timeout and hyper's built-in timeouts let app = Router::new() .route("/health", get(handle_health)) .route("/pair", post(handle_pair)) @@ -172,7 +172,11 @@ pub async fn run_gateway(host: &str, port: u16, config: Config) -> Result<()> { .route("/whatsapp", get(handle_whatsapp_verify)) .route("/whatsapp", post(handle_whatsapp_message)) .with_state(state) - .layer(RequestBodyLimitLayer::new(MAX_BODY_SIZE)); + .layer(RequestBodyLimitLayer::new(MAX_BODY_SIZE)) + .layer(TimeoutLayer::with_status_code( + StatusCode::REQUEST_TIMEOUT, + Duration::from_secs(REQUEST_TIMEOUT_SECS), + )); // Run the server axum::serve(listener, app).await?; diff --git a/src/security/secrets.rs b/src/security/secrets.rs index bafad38..3940843 100644 --- a/src/security/secrets.rs +++ b/src/security/secrets.rs @@ -241,7 +241,7 @@ fn hex_encode(data: &[u8]) -> String { /// Hex-decode a hex string to bytes. fn hex_decode(hex: &str) -> Result> { - if hex.len() % 2 != 0 { + if !hex.len().is_multiple_of(2) { anyhow::bail!("Hex string has odd length"); } (0..hex.len()) diff --git a/src/tools/browser.rs b/src/tools/browser.rs index 5ee9505..25be13c 100644 --- a/src/tools/browser.rs +++ b/src/tools/browser.rs @@ -366,6 +366,7 @@ impl BrowserTool { } #[async_trait] +#[allow(clippy::too_many_lines)] impl Tool for BrowserTool { fn name(&self) -> &str { "browser"