From b23fae73187fa06a4c7e6c778cc9dec05ba65655 Mon Sep 17 00:00:00 2001 From: CyberSecurityUP Date: Sat, 19 Sep 2026 18:24:34 -0300 Subject: [PATCH] fix(transport): exempt loopback from the fail-closed gate (unambiguous host) 127.0.0.1/localhost/::1 always mean this host regardless of any VPN, so the network-position ambiguity the gate guards against does not apply. Needed to run against a local benchmark target. Co-Authored-By: Claude Opus 5 (1M context) --- neurosploit-rs/crates/harness/src/transport.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/neurosploit-rs/crates/harness/src/transport.rs b/neurosploit-rs/crates/harness/src/transport.rs index 6d113c6..e870430 100644 --- a/neurosploit-rs/crates/harness/src/transport.rs +++ b/neurosploit-rs/crates/harness/src/transport.rs @@ -139,6 +139,16 @@ impl Egress { /// `.internal`/`.corp` style names. These are the addresses that resolve to /// something different depending on which network you are on, which is exactly /// the condition that makes a missing VPN dangerous rather than merely broken. +/// Loopback target — unambiguous, exempt from the fail-closed transport gate. +pub fn is_loopback_target(target: &str) -> bool { + let host = target.rsplit("://").next().unwrap_or(target) + .split('/').next().unwrap_or("") + .rsplit('@').next().unwrap_or("") + .trim_end_matches('.').to_lowercase(); + let host = if let Some(rest) = host.strip_prefix('[') { rest.split(']').next().unwrap_or(rest).to_string() } else { host.split(':').next().unwrap_or(&host).to_string() }; + host == "localhost" || host == "127.0.0.1" || host == "::1" || host.starts_with("127.") +} + pub fn is_internal(target: &str) -> bool { let host = target .rsplit("://") @@ -218,7 +228,11 @@ impl Transport { /// and it is a refusal rather than a warning because a warning in a log /// nobody is reading is not a control. pub fn admits(&self, target: &str) -> Result<(), String> { - if self.egress == Egress::Direct && is_internal(target) { + // Loopback is exempt: 127.0.0.1 / localhost / ::1 unambiguously mean + // THIS host, regardless of any VPN — there is no network-position + // ambiguity to fail closed on. The gate exists for RFC1918-style + // addresses that resolve to different machines depending on the route. + if self.egress == Egress::Direct && is_internal(target) && !is_loopback_target(target) { return Err(format!( "{target} is an internal address and no transport is configured. \ With the VPN or bastion down this address belongs to whatever network this host is on, \