use super::*;
#[test]
fn to_redacted_toml_hides_mcp_secrets_but_keeps_shape() {
let cfg: Config = toml::from_str(
r#"
[[backends]]
name = "remote"
endpoint = "http://remote:8000"
model = "qwen3:32b"
tiers = []
kind = "openai"
api_key_file = "~/.newt/openai.key"
[[mcp_servers]]
name = "gh"
type = "http"
url = "https://api.example/mcp"
[mcp_servers.headers]
Authorization = "Bearer sk-super-secret-token"
[mcp_servers.env]
GH_TOKEN = "ghp_rawsecretvalue"
RUST_LOG = "debug"
"#,
)
.unwrap();
let dump = cfg.to_redacted_toml().unwrap();
assert!(
!dump.contains("sk-super-secret-token"),
"header secret leaked:\n{dump}"
);
assert!(
!dump.contains("ghp_rawsecretvalue"),
"env secret leaked:\n{dump}"
);
assert!(dump.contains("Authorization"));
assert!(dump.contains("GH_TOKEN"));
assert!(dump.contains(Config::REDACTED));
assert!(
dump.contains("~/.newt/openai.key"),
"api_key_file reference kept"
);
assert!(dump.contains("http://remote:8000"));
}
#[test]
fn to_redacted_toml_redacts_literals_but_keeps_secret_references() {
let cfg: Config = toml::from_str(
r#"
[[mcp_servers]]
name = "gh"
command = "gh-mcp"
[mcp_servers.env]
RAW = "ghp_rawinlinesecret"
VAULTED = { cmd = "vault kv get -field=token secret/data/gh" }
[mcp_servers.headers]
Authorization = "Bearer ${cmd:vault kv get -field=token secret/data/api}"
"#,
)
.unwrap();
let dump = cfg.to_redacted_toml().unwrap();
assert!(
!dump.contains("ghp_rawinlinesecret"),
"raw secret leaked:\n{dump}"
);
assert!(
!dump.contains("secret/data/api"),
"interpolation literal leaked:\n{dump}"
);
assert!(dump.contains(Config::REDACTED));
assert!(dump.contains("RAW"));
assert!(dump.contains("VAULTED"));
assert!(dump.contains("Authorization"));
assert!(
dump.contains("vault kv get -field=token secret/data/gh"),
"SecretRef reference kept:\n{dump}"
);
}
#[test]
fn to_redacted_toml_redacts_url_userinfo_query_and_args() {
let cfg: Config = toml::from_str(
r#"
[[mcp_servers]]
name = "gh"
type = "http"
url = "https://alice:sk-URLPASS@api.example/mcp?api_key=SECRETQP®ion=us"
args = ["--token=sk-EQ", "--api-key", "sk-SPACE", "--verbose"]
"#,
)
.unwrap();
let dump = cfg.to_redacted_toml().unwrap();
for leaked in ["sk-URLPASS", "SECRETQP", "sk-EQ", "sk-SPACE", "alice"] {
assert!(!dump.contains(leaked), "`{leaked}` leaked:\n{dump}");
}
assert!(dump.contains("api.example/mcp"), "host/path kept:\n{dump}");
assert!(dump.contains("region=us"), "non-secret param kept:\n{dump}");
assert!(dump.contains("--verbose"), "non-secret arg kept:\n{dump}");
assert!(dump.contains(Config::REDACTED));
}
#[test]
fn redact_url_and_args_helpers_are_precise() {
assert_eq!(
redact_url_secrets("https://u:p@h.example/mcp?token=abc&keep=1#frag"),
format!(
"https://{r}@h.example/mcp?token={r}&keep=1#frag",
r = Config::REDACTED
)
);
assert_eq!(
redact_url_secrets("https://h.example/mcp?region=us"),
"https://h.example/mcp?region=us"
);
assert_eq!(
redact_url_secrets("https://h.example/a@b"),
"https://h.example/a@b"
);
assert_eq!(
redact_arg_secrets(&[
"--token=sk-1".into(),
"--api-key".into(),
"sk-2".into(),
"--model".into(),
"gpt".into(),
]),
vec![
format!("--token={}", Config::REDACTED),
"--api-key".to_string(),
Config::REDACTED.to_string(),
"--model".to_string(),
"gpt".to_string(),
]
);
}
#[test]
fn redact_url_and_args_helpers_normalize_credential_spellings() {
let dump = redact_url_secrets(
"https://h.example/mcp?client%5Fsecret=one&refresh-token=two&X-Amz-Signature=three®ion=us",
);
for leaked in ["one", "two", "three"] {
assert!(!dump.contains(leaked), "`{leaked}` leaked: {dump}");
}
assert!(dump.contains("region=us"));
assert_eq!(
redact_arg_secrets(&[
"--access-token=one".into(),
"--client_secret".into(),
"two".into(),
"-H".into(),
"Authorization: Bearer three".into(),
"--header=X-API-Key: four".into(),
"-H".into(),
"X-Auth-Token: five".into(),
"-HX-Client-Secret: six".into(),
"--auth=seven".into(),
"--oauth2-bearer".into(),
"eight".into(),
"-uuser:nine".into(),
"-b".into(),
"ten".into(),
"--cookie=eleven".into(),
"--header=X-Trace: keep".into(),
]),
vec![
format!("--access-token={}", Config::REDACTED),
"--client_secret".to_string(),
Config::REDACTED.to_string(),
"-H".to_string(),
Config::REDACTED.to_string(),
format!("--header={}", Config::REDACTED),
"-H".to_string(),
Config::REDACTED.to_string(),
format!("-H{}", Config::REDACTED),
format!("--auth={}", Config::REDACTED),
"--oauth2-bearer".to_string(),
Config::REDACTED.to_string(),
format!("-u{}", Config::REDACTED),
"-b".to_string(),
Config::REDACTED.to_string(),
format!("--cookie={}", Config::REDACTED),
"--header=X-Trace: keep".to_string(),
]
);
}