From 6c14d669b8cde5edbb516c0619c5b671d553c70a Mon Sep 17 00:00:00 2001 From: DuProcess <273172371+DuProcess@users.noreply.github.com> Date: Sun, 14 Jun 2026 09:10:09 -0400 Subject: [PATCH] Gate non-loopback remote binds --- docs/NATIVE_V1_SPEC.md | 1 + src/bin/dosh-server.rs | 28 ++++++++++++++++++++++++---- src/config.rs | 3 +++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/NATIVE_V1_SPEC.md b/docs/NATIVE_V1_SPEC.md index 55825d1..0c39a11 100644 --- a/docs/NATIVE_V1_SPEC.md +++ b/docs/NATIVE_V1_SPEC.md @@ -506,6 +506,7 @@ native_auth_rate_limit_per_minute = 30 attach_ticket_ttl_secs = 86400 allow_tcp_forwarding = true allow_remote_forwarding = false +allow_remote_non_loopback_bind = false allow_agent_forwarding = false accept_env = ["LANG", "LC_*", "TERM", "COLORTERM"] ``` diff --git a/src/bin/dosh-server.rs b/src/bin/dosh-server.rs index 8317585..5b43a75 100644 --- a/src/bin/dosh-server.rs +++ b/src/bin/dosh-server.rs @@ -1443,10 +1443,7 @@ async fn start_remote_forwards( .bind_host .clone() .unwrap_or_else(|| "127.0.0.1".to_string()); - anyhow::ensure!( - is_loopback_bind(&bind_host), - "remote forwarding may only bind loopback in this server build" - ); + remote_bind_allowed(&config, &bind_host)?; let listen_port = forward.listen_port; let target_host = forward .target_host @@ -1547,6 +1544,16 @@ fn is_loopback_bind(host: &str) -> bool { matches!(host, "127.0.0.1" | "localhost" | "::1") } +fn remote_bind_allowed(config: &ServerConfig, host: &str) -> Result<()> { + if !is_loopback_bind(host) { + anyhow::ensure!( + config.allow_remote_non_loopback_bind, + "remote forwarding non-loopback bind disabled" + ); + } + Ok(()) +} + fn stream_open_allowed( config: &ServerConfig, client: &ClientState, @@ -2151,4 +2158,17 @@ mod tests { assert_eq!(data.stream_id, 42); assert_eq!(data.bytes, b"hello"); } + + #[test] + fn remote_non_loopback_bind_requires_explicit_config() { + let config = ServerConfig::default(); + remote_bind_allowed(&config, "127.0.0.1").unwrap(); + assert!(remote_bind_allowed(&config, "0.0.0.0").is_err()); + + let config = ServerConfig { + allow_remote_non_loopback_bind: true, + ..ServerConfig::default() + }; + remote_bind_allowed(&config, "0.0.0.0").unwrap(); + } } diff --git a/src/config.rs b/src/config.rs index c51bf75..0fb0f45 100644 --- a/src/config.rs +++ b/src/config.rs @@ -33,6 +33,8 @@ pub struct ServerConfig { #[serde(default)] pub allow_remote_forwarding: bool, #[serde(default)] + pub allow_remote_non_loopback_bind: bool, + #[serde(default)] pub allow_agent_forwarding: bool, #[serde(default = "default_accept_env")] pub accept_env: Vec, @@ -61,6 +63,7 @@ impl Default for ServerConfig { native_auth_rate_limit_per_minute: default_native_auth_rate_limit_per_minute(), allow_tcp_forwarding: true, allow_remote_forwarding: false, + allow_remote_non_loopback_bind: false, allow_agent_forwarding: false, accept_env: default_accept_env(), }