mod common;
use kz_proxy::{Sandbox, SandboxConfig, SecretMapping, StringMapping};
use std::time::Duration;
#[tokio::test]
async fn e2e_single_secret_in_header() {
if !common::curl_available() {
eprintln!("skipping e2e_single_secret_in_header: curl not on PATH");
return;
}
let (port, handle, captured) = common::start_capture_server().expect("capture server");
let config = SandboxConfig {
secrets: vec![SecretMapping {
var: "SLACK_TOKEN".to_string(),
value: "real-token-123".to_string(),
}],
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = format!(
"curl -s -x $HTTP_PROXY -H \"Authorization: Bearer $SLACK_TOKEN\" http://127.0.0.1:{}/",
port
);
let result = tokio::time::timeout(Duration::from_secs(5), sandbox.run("sh", &["-c".to_string(), cmd])).await;
let _ = handle.join();
let req = captured.lock().unwrap().clone();
assert!(result.is_ok(), "timeout or run failed: {:?}", result);
let run_result = result.unwrap();
assert!(
run_result.is_ok(),
"sandbox.run failed: {:?}",
run_result.err()
);
assert!(
req.contains("real-token-123"),
"captured request should contain real token (rewritten from masked), got: {}",
req
);
}
#[tokio::test]
async fn e2e_single_secret_in_body() {
if !common::curl_available() {
eprintln!("skipping e2e_single_secret_in_body: curl not on PATH");
return;
}
let (port, handle, captured) = common::start_capture_server().expect("capture server");
let config = SandboxConfig {
secrets: vec![SecretMapping {
var: "API_KEY".to_string(),
value: "key456".to_string(),
}],
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = format!(
"curl -s -x $HTTP_PROXY -X POST -d \"key=$API_KEY\" http://127.0.0.1:{}/",
port
);
let result = tokio::time::timeout(Duration::from_secs(5), sandbox.run("sh", &["-c".to_string(), cmd])).await;
let _ = handle.join();
let req = captured.lock().unwrap().clone();
assert!(result.is_ok());
let run_result = result.unwrap();
assert!(run_result.is_ok());
assert!(
req.contains("key456"),
"captured body should contain real secret, got: {}",
req
);
}
#[tokio::test]
async fn e2e_method_get() {
if !common::curl_available() {
eprintln!("skipping e2e_method_get: curl not on PATH");
return;
}
let (port, handle, captured) = common::start_capture_server().expect("capture server");
let config = SandboxConfig {
secrets: vec![SecretMapping {
var: "X".to_string(),
value: "y".to_string(),
}],
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = format!("curl -s -x $HTTP_PROXY http://127.0.0.1:{}/", port);
let result = tokio::time::timeout(Duration::from_secs(5), sandbox.run("sh", &["-c".to_string(), cmd])).await;
let _ = handle.join();
let req = captured.lock().unwrap().clone();
assert!(result.is_ok());
assert!(result.unwrap().unwrap().success());
assert!(
req.starts_with("GET "),
"expected GET, got: {}",
req.lines().next().unwrap_or("")
);
}
#[tokio::test]
async fn e2e_empty_body_get() {
if !common::curl_available() {
eprintln!("skipping e2e_empty_body_get: curl not on PATH");
return;
}
let (port, handle, captured) = common::start_capture_server().expect("capture server");
let config = SandboxConfig {
secrets: vec![SecretMapping {
var: "X".to_string(),
value: "y".to_string(),
}],
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = format!("curl -s -x $HTTP_PROXY http://127.0.0.1:{}/", port);
let result = tokio::time::timeout(Duration::from_secs(5), sandbox.run("sh", &["-c".to_string(), cmd])).await;
let _ = handle.join();
let req = captured.lock().unwrap().clone();
assert!(result.is_ok());
let run_result = result.unwrap();
assert!(run_result.is_ok());
assert!(req.contains("GET ") || req.contains("get "));
}
#[tokio::test]
async fn e2e_upstream_unreachable_502() {
if !common::curl_available() {
eprintln!("skipping e2e_upstream_unreachable_502: curl not on PATH");
return;
}
let config = SandboxConfig {
secrets: vec![SecretMapping {
var: "X".to_string(),
value: "y".to_string(),
}],
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = "curl -s -x $HTTP_PROXY -o /dev/null -w '%{http_code}' http://127.0.0.1:1/".to_string();
let result = tokio::time::timeout(Duration::from_secs(5), sandbox.run("sh", &["-c".to_string(), cmd])).await;
assert!(result.is_ok());
let _ = result.unwrap();
}
#[tokio::test]
async fn e2e_connect_https_tunnel() {
if !common::curl_available() {
eprintln!("skipping e2e_connect_https_tunnel: curl not on PATH");
return;
}
let config = SandboxConfig {
secrets: vec![SecretMapping {
var: "X".to_string(),
value: "y".to_string(),
}],
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = "curl -s -x $HTTP_PROXY -o /dev/null -w '%{http_code}' https://example.com/".to_string();
let result = tokio::time::timeout(
Duration::from_secs(10),
sandbox.run("sh", &["-c".to_string(), cmd]),
)
.await;
assert!(result.is_ok(), "timeout or run failed");
let run_result = result.unwrap();
assert!(
run_result.is_ok(),
"sandbox.run failed: {:?}",
run_result.err()
);
let status = run_result.unwrap();
if !status.success() {
eprintln!("note: curl exited {:?} (network may be unreachable); CONNECT tunnel implementation is exercised", status.code());
}
}
#[tokio::test]
async fn e2e_connect_https_mitm_rewrites_token() {
if !common::curl_available() {
eprintln!("skipping e2e_connect_https_mitm_rewrites_token: curl not on PATH");
return;
}
let (port, handle, captured, server_cert_path) =
common::start_https_capture_server().expect("https capture server");
let config = SandboxConfig {
secrets: vec![SecretMapping {
var: "BEARER_TOKEN".to_string(),
value: "secret-rewritten-via-mitm".to_string(),
}],
allow_private_connect: true,
upstream_ca: Some(server_cert_path),
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = format!(
"curl -s -k -x $HTTPS_PROXY -H \"Authorization: Bearer $BEARER_TOKEN\" https://127.0.0.1:{}/",
port
);
let result = tokio::time::timeout(Duration::from_secs(10), sandbox.run("sh", &["-c".to_string(), cmd])).await;
let _ = handle.join();
assert!(result.is_ok(), "timeout or run failed: {:?}", result);
let run_result = result.unwrap();
assert!(
run_result.is_ok(),
"sandbox.run failed: {:?}",
run_result.err()
);
assert!(run_result.unwrap().success());
let req = captured.lock().unwrap().clone();
assert!(
req.contains("secret-rewritten-via-mitm"),
"MITM should rewrite token in HTTPS request; got: {}",
req
);
}
#[tokio::test]
async fn e2e_connect_https_tunnel_local() {
if !common::curl_available() {
eprintln!("skipping e2e_connect_https_tunnel_local: curl not on PATH");
return;
}
let (port, handle, captured, server_cert_path) =
common::start_https_capture_server().expect("https capture server");
let config = SandboxConfig {
secrets: vec![SecretMapping {
var: "X".to_string(),
value: "y".to_string(),
}],
allow_private_connect: true,
upstream_ca: Some(server_cert_path),
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = format!(
"curl -s -k -x $HTTPS_PROXY -o /dev/null -w '%{{http_code}}' https://127.0.0.1:{}/",
port
);
let result = tokio::time::timeout(Duration::from_secs(10), sandbox.run("sh", &["-c".to_string(), cmd])).await;
let _ = handle.join();
assert!(result.is_ok(), "timeout or run failed: {:?}", result);
let run_result = result.unwrap();
assert!(
run_result.is_ok(),
"sandbox.run failed: {:?}",
run_result.err()
);
let status = run_result.unwrap();
assert!(status.success(), "curl should succeed: {:?}", status.code());
let req = captured.lock().unwrap().clone();
assert!(
req.starts_with("GET ") || req.to_lowercase().starts_with("get "),
"expected GET request over TLS tunnel, got: {}",
req.lines().next().unwrap_or("")
);
}
#[tokio::test]
async fn e2e_secret_in_url_query() {
if !common::curl_available() {
eprintln!("skipping e2e_secret_in_url_query: curl not on PATH");
return;
}
let (port, handle, captured) = common::start_capture_server().expect("capture server");
let config = SandboxConfig {
secrets: vec![SecretMapping {
var: "TOKEN".to_string(),
value: "qval".to_string(),
}],
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = format!(
"curl -s -x $HTTP_PROXY \"http://127.0.0.1:{}/?t=$TOKEN\"",
port
);
let result = tokio::time::timeout(Duration::from_secs(5), sandbox.run("sh", &["-c".to_string(), cmd])).await;
let _ = handle.join();
let req = captured.lock().unwrap().clone();
assert!(result.is_ok());
assert!(result.unwrap().unwrap().success());
assert!(
req.contains("qval"),
"URL query should contain rewritten secret, got: {}",
req
);
}
#[tokio::test]
async fn e2e_small_body_post_forwarded() {
if !common::curl_available() {
eprintln!("skipping e2e_small_body_post_forwarded: curl not on PATH");
return;
}
let (port, handle, captured) = common::start_capture_server().expect("capture server");
let config = SandboxConfig {
secrets: vec![SecretMapping {
var: "X".to_string(),
value: "y".to_string(),
}],
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let body = "key=value";
let cmd = format!(
"curl -s -x $HTTP_PROXY -X POST -d \"{}\" http://127.0.0.1:{}/",
body, port
);
let result = tokio::time::timeout(Duration::from_secs(5), sandbox.run("sh", &["-c".to_string(), cmd])).await;
let _ = handle.join();
let req = captured.lock().unwrap().clone();
assert!(result.is_ok());
assert!(result.unwrap().unwrap().success());
assert!(
req.contains("key=value"),
"small POST body should be forwarded: {}",
req
);
}
#[tokio::test]
async fn e2e_multiple_secrets() {
if !common::curl_available() {
eprintln!("skipping e2e_multiple_secrets: curl not on PATH");
return;
}
let (port, handle, captured) = common::start_capture_server().expect("capture server");
let config = SandboxConfig {
secrets: vec![
SecretMapping {
var: "A".to_string(),
value: "secret-a".to_string(),
},
SecretMapping {
var: "B".to_string(),
value: "secret-b".to_string(),
},
],
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = format!(
"curl -s -x $HTTP_PROXY -H \"X-A: $A\" -H \"X-B: $B\" http://127.0.0.1:{}/",
port
);
let result = tokio::time::timeout(Duration::from_secs(5), sandbox.run("sh", &["-c".to_string(), cmd])).await;
let _ = handle.join();
let req = captured.lock().unwrap().clone();
assert!(result.is_ok());
assert!(result.unwrap().unwrap().success());
assert!(
req.contains("secret-a"),
"expected secret-a in request: {}",
req
);
assert!(
req.contains("secret-b"),
"expected secret-b in request: {}",
req
);
}
#[tokio::test]
async fn e2e_string_mapping_in_header() {
if !common::curl_available() {
eprintln!("skipping e2e_string_mapping_in_header: curl not on PATH");
return;
}
let (port, handle, captured) = common::start_capture_server().expect("capture server");
let config = SandboxConfig {
strings: vec![StringMapping {
token: "__BEARER__".to_string(),
value: "replaced-bearer-token".to_string(),
}],
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = format!(
"curl -s -x $HTTP_PROXY -H \"Authorization: Bearer __BEARER__\" http://127.0.0.1:{}/",
port
);
let result = tokio::time::timeout(Duration::from_secs(5), sandbox.run("sh", &["-c".to_string(), cmd])).await;
let _ = handle.join();
let req = captured.lock().unwrap().clone();
assert!(result.is_ok());
let run_result = result.unwrap();
assert!(run_result.is_ok());
assert!(
req.contains("replaced-bearer-token"),
"string mapping should replace token in header, got: {}",
req
);
}
#[tokio::test]
async fn e2e_string_and_secret_mapping() {
if !common::curl_available() {
eprintln!("skipping e2e_string_and_secret_mapping: curl not on PATH");
return;
}
let (port, handle, captured) = common::start_capture_server().expect("capture server");
let config = SandboxConfig {
secrets: vec![SecretMapping {
var: "API_KEY".to_string(),
value: "env-secret".to_string(),
}],
strings: vec![StringMapping {
token: "__PLACEHOLDER__".to_string(),
value: "string-replaced".to_string(),
}],
..SandboxConfig::default()
};
let sandbox = Sandbox::new(config);
let cmd = format!(
"curl -s -x $HTTP_PROXY -H \"X-Api-Key: $API_KEY\" -H \"X-Token: __PLACEHOLDER__\" http://127.0.0.1:{}/",
port
);
let result = tokio::time::timeout(Duration::from_secs(5), sandbox.run("sh", &["-c".to_string(), cmd])).await;
let _ = handle.join();
let req = captured.lock().unwrap().clone();
assert!(result.is_ok());
assert!(result.unwrap().unwrap().success());
assert!(
req.contains("env-secret"),
"expected env secret in request: {}",
req
);
assert!(
req.contains("string-replaced"),
"expected string mapping in request: {}",
req
);
}