From f4f868705c19d31f40a0209aec45ac0bc3865d11 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Mar 2026 02:05:46 +0000 Subject: [PATCH 1/2] fix(network): redact credentials from URL in allowlist error messages URLs with user:pass@ in the authority are now redacted before inclusion in "not in allowlist" error messages. Closes #429 --- crates/bashkit/src/network/allowlist.rs | 43 ++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/bashkit/src/network/allowlist.rs b/crates/bashkit/src/network/allowlist.rs index 3a7c8ce2..f7b53c11 100644 --- a/crates/bashkit/src/network/allowlist.rs +++ b/crates/bashkit/src/network/allowlist.rs @@ -17,6 +17,21 @@ use std::collections::HashSet; use url::Url; +/// Redact credentials from a URL for safe inclusion in error messages. +/// Replaces `user:pass@` in the authority with `***@`. +fn redact_url(url: &str) -> String { + match Url::parse(url) { + Ok(mut parsed) => { + if !parsed.username().is_empty() || parsed.password().is_some() { + let _ = parsed.set_username("***"); + let _ = parsed.set_password(None); + } + parsed.to_string() + } + Err(_) => "[invalid URL]".to_string(), + } +} + /// Network allowlist configuration for controlling HTTP access. /// /// URLs must match an entry in the allowlist to be accessed. @@ -141,7 +156,7 @@ impl NetworkAllowlist { } UrlMatch::Blocked { - reason: format!("URL not in allowlist: {}", url), + reason: format!("URL not in allowlist: {}", redact_url(url)), } } @@ -368,4 +383,30 @@ mod tests { let allow_all = NetworkAllowlist::allow_all(); assert!(allow_all.is_enabled()); } + + #[test] + fn test_redact_url_strips_credentials() { + let redacted = redact_url("https://user:secret@example.com/path"); + assert!(!redacted.contains("secret"), "password leaked: {}", redacted); + assert!(!redacted.contains("user"), "username leaked: {}", redacted); + assert!(redacted.contains("example.com/path")); + } + + #[test] + fn test_redact_url_preserves_clean_url() { + let clean = "https://example.com/path?q=1"; + assert_eq!(redact_url(clean), clean); + } + + #[test] + fn test_blocked_message_no_credentials() { + let allowlist = NetworkAllowlist::new().allow("https://allowed.com"); + let result = allowlist.check("https://user:pass@blocked.com/api"); + match result { + UrlMatch::Blocked { reason } => { + assert!(!reason.contains("pass"), "credentials leaked: {}", reason); + } + _ => panic!("expected Blocked"), + } + } } From 684c1572d8982f745104018dfaf56fbb53416684 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Mar 2026 02:47:42 +0000 Subject: [PATCH 2/2] style: fix formatting --- crates/bashkit/src/network/allowlist.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/bashkit/src/network/allowlist.rs b/crates/bashkit/src/network/allowlist.rs index f7b53c11..610970ca 100644 --- a/crates/bashkit/src/network/allowlist.rs +++ b/crates/bashkit/src/network/allowlist.rs @@ -387,7 +387,11 @@ mod tests { #[test] fn test_redact_url_strips_credentials() { let redacted = redact_url("https://user:secret@example.com/path"); - assert!(!redacted.contains("secret"), "password leaked: {}", redacted); + assert!( + !redacted.contains("secret"), + "password leaked: {}", + redacted + ); assert!(!redacted.contains("user"), "username leaked: {}", redacted); assert!(redacted.contains("example.com/path")); }