#![cfg(feature = "http_client")]
use bashkit::{Bash, NetworkAllowlist};
mod allowlist {
use super::*;
#[tokio::test]
async fn threat_empty_allowlist_blocks_all() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("curl https://example.com").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("access denied"));
}
#[tokio::test]
async fn threat_allowlist_blocks_unlisted() {
let allowlist = NetworkAllowlist::new().allow("https://allowed.com");
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("curl https://blocked.com").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("access denied"));
}
#[tokio::test]
async fn threat_ip_bypass_blocked() {
let allowlist = NetworkAllowlist::new().allow("https://example.com");
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("curl https://93.184.216.34").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("access denied"));
}
#[tokio::test]
async fn threat_port_bypass_blocked() {
let allowlist = NetworkAllowlist::new().allow("https://example.com");
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("curl https://example.com:8443").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("access denied"));
}
#[tokio::test]
async fn threat_scheme_downgrade_blocked() {
let allowlist = NetworkAllowlist::new().allow("https://example.com");
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("curl http://example.com").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("access denied"));
}
#[tokio::test]
async fn threat_subdomain_bypass_blocked() {
let allowlist = NetworkAllowlist::new().allow("https://example.com");
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("curl https://evil.example.com").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("access denied"));
}
#[tokio::test]
async fn threat_path_prefix_enforced() {
let allowlist = NetworkAllowlist::new().allow("https://api.example.com/v1");
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("curl https://api.example.com/v2").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("access denied"));
}
#[tokio::test]
async fn threat_wget_respects_allowlist() {
let allowlist = NetworkAllowlist::new().allow("https://allowed.com");
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("wget https://blocked.com").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("access denied"));
}
}
mod no_network {
use super::*;
#[tokio::test]
async fn curl_without_network_config() {
let mut bash = Bash::new();
let result = bash.exec("curl https://example.com").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("network access not configured"));
}
#[tokio::test]
async fn wget_without_network_config() {
let mut bash = Bash::new();
let result = bash.exec("wget https://example.com").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("network access not configured"));
}
}
mod curl_args {
use super::*;
#[tokio::test]
async fn curl_requires_url() {
let mut bash = Bash::new();
let result = bash.exec("curl").await.unwrap();
assert_eq!(result.exit_code, 3);
assert!(result.stderr.contains("no URL specified"));
}
#[tokio::test]
async fn curl_method_parsing() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("curl -X POST https://example.com").await.unwrap();
assert!(result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_data_implies_post() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl -d 'data=test' https://example.com")
.await
.unwrap();
assert!(result.stderr.contains("access denied"));
}
}
mod wget_args {
use super::*;
#[tokio::test]
async fn wget_requires_url() {
let mut bash = Bash::new();
let result = bash.exec("wget").await.unwrap();
assert_eq!(result.exit_code, 1);
assert!(result.stderr.contains("missing URL"));
}
#[tokio::test]
async fn wget_quiet_mode() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("wget -q https://example.com").await.unwrap();
assert!(result.stderr.contains("access denied"));
}
}
mod script_integration {
use super::*;
#[tokio::test]
async fn curl_in_pipeline() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl https://blocked.com 2>&1 | grep -c denied")
.await
.unwrap();
assert!(result.stdout.trim() == "1" || result.exit_code != 0);
}
#[tokio::test]
async fn curl_exit_code_in_condition() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec(
r#"
if curl https://blocked.com 2>/dev/null; then
echo "success"
else
echo "failed"
fi
"#,
)
.await
.unwrap();
assert!(result.stdout.contains("failed"));
}
#[tokio::test]
async fn wget_output_redirect() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("wget -O output.txt https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
}
mod security_edge_cases {
use super::*;
#[tokio::test]
async fn curl_malformed_url() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("curl not-a-url").await.unwrap();
assert_ne!(result.exit_code, 0);
}
#[tokio::test]
async fn curl_very_long_url() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let long_path = "a".repeat(10000);
let script = format!("curl https://example.com/{}", long_path);
let result = bash.exec(&script).await.unwrap();
assert_ne!(result.exit_code, 0);
}
#[tokio::test]
async fn curl_url_with_special_chars() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl 'https://example.com/path?a=1&b=2'")
.await
.unwrap();
assert!(result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_in_command_substitution() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("echo $(curl https://blocked.com 2>&1)")
.await
.unwrap();
assert!(result.stdout.contains("access denied"));
}
#[tokio::test]
async fn curl_with_variable_url() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec(
r#"
URL="https://blocked.com"
curl $URL 2>&1
"#,
)
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
}
mod allowlist_unit {
use bashkit::NetworkAllowlist;
#[test]
fn test_empty_allowlist() {
let allowlist = NetworkAllowlist::new();
assert!(!allowlist.is_allowed("https://example.com"));
}
#[test]
fn test_allow_all() {
let allowlist = NetworkAllowlist::allow_all();
assert!(allowlist.is_allowed("https://example.com"));
assert!(allowlist.is_allowed("http://any.domain.com:8080/path"));
}
#[test]
fn test_specific_host() {
let allowlist = NetworkAllowlist::new().allow("https://api.example.com");
assert!(allowlist.is_allowed("https://api.example.com"));
assert!(allowlist.is_allowed("https://api.example.com/any/path"));
assert!(!allowlist.is_allowed("https://other.example.com"));
}
#[test]
fn test_path_prefix() {
let allowlist = NetworkAllowlist::new().allow("https://api.example.com/v1");
assert!(allowlist.is_allowed("https://api.example.com/v1"));
assert!(allowlist.is_allowed("https://api.example.com/v1/users"));
assert!(!allowlist.is_allowed("https://api.example.com/v2"));
assert!(!allowlist.is_allowed("https://api.example.com/"));
}
#[test]
fn test_multiple_allowed() {
let allowlist = NetworkAllowlist::new()
.allow("https://api.example.com")
.allow("https://cdn.example.com");
assert!(allowlist.is_allowed("https://api.example.com"));
assert!(allowlist.is_allowed("https://cdn.example.com"));
assert!(!allowlist.is_allowed("https://other.example.com"));
}
#[test]
fn test_port_matching() {
let allowlist = NetworkAllowlist::new().allow("https://example.com:8443");
assert!(allowlist.is_allowed("https://example.com:8443"));
assert!(!allowlist.is_allowed("https://example.com")); assert!(!allowlist.is_allowed("https://example.com:443"));
}
#[test]
fn test_scheme_matching() {
let allowlist = NetworkAllowlist::new().allow("https://example.com");
assert!(allowlist.is_allowed("https://example.com"));
assert!(!allowlist.is_allowed("http://example.com"));
}
}
mod curl_flags {
use super::*;
#[tokio::test]
async fn curl_compressed_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl --compressed https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_user_auth_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl -u user:pass https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("curl --user admin:secret https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_user_agent_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl -A 'Mozilla/5.0' https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("curl --user-agent 'CustomAgent/1.0' https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_referer_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl -e 'https://example.com' https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_verbose_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash.exec("curl -v https://blocked.com 2>&1").await.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_max_time_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl -m 10 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("curl --max-time 30 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_max_time_various_values() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl -m 1 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("curl -m 300 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("curl -m 0 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.exit_code != 0);
}
#[tokio::test]
async fn curl_max_time_with_other_flags() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl -s -m 5 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("curl -m 5 -d 'data=test' https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("curl -m 5 -H 'X-Custom: value' https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_timeout_error_exit_code() {
let allowlist = NetworkAllowlist::new().allow("https://example.com");
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl -m 5 https://notinallowlist.com 2>&1; echo \"exit:$?\"")
.await
.unwrap();
assert!(result.stdout.contains("exit:7") || result.stdout.contains("access denied"));
}
#[tokio::test]
async fn curl_connect_timeout_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl --connect-timeout 5 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("curl --connect-timeout 30 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_both_timeout_flags() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl -m 30 --connect-timeout 5 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("curl --connect-timeout 60 -m 10 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_timeout_safety_limits() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl -m 999999 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("curl -m 0 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("curl --connect-timeout 999999 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn curl_combined_flags() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl -s --compressed -u user:pass -A 'Agent' -e 'http://ref.com' -m 30 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
}
mod wget_flags {
use super::*;
#[tokio::test]
async fn wget_header_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("wget --header 'X-Custom: value' https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn wget_user_agent_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("wget -U 'CustomAgent' https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("wget --user-agent 'Mozilla/5.0' https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn wget_post_data_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("wget --post-data 'key=value' https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn wget_tries_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("wget -t 3 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("wget --tries 5 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn wget_combined_flags() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("wget -q --header 'Accept: application/json' -U 'Agent' -t 3 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn wget_timeout_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("wget -T 10 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("wget --timeout 30 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn wget_connect_timeout_flag() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("wget --connect-timeout 5 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn wget_both_timeout_flags() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("wget -T 30 --connect-timeout 5 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
#[tokio::test]
async fn wget_timeout_safety_limits() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("wget -T 999999 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
let result = bash
.exec("wget -T 0 https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
}
mod decompression_security {
use super::*;
#[tokio::test]
async fn compressed_respects_allowlist() {
let allowlist = NetworkAllowlist::new();
let mut bash = Bash::builder().network(allowlist).build();
let result = bash
.exec("curl --compressed https://blocked.com 2>&1")
.await
.unwrap();
assert!(result.stdout.contains("access denied") || result.stderr.contains("access denied"));
}
}