use super::redaction::{collect_secret_values, redact_transcript};
use crate::core::types::{Resource, ResourceType, SecretsConfig};
const SECRET: &str = "hunter2-Zq7x4Kv9Lm2Rw8Tn";
fn b64(s: &str) -> String {
use crate::core::secrets::B64;
use base64::Engine;
B64.encode(s.as_bytes())
}
#[test]
fn literal_secret_is_struck_from_the_transcript() {
let script = format!("printf '%s' 'literal={SECRET}' > /tmp/x");
let out = redact_transcript(&script, &[SECRET.to_string()]);
assert!(!out.contains(SECRET), "{out}");
assert!(out.contains("printf '%s' 'literal=***'"), "{out}");
}
#[test]
fn base64_encoded_secret_is_struck_even_though_the_value_never_appears() {
let content = format!("api_token={SECRET}\n");
let blob = b64(&content);
assert!(
!blob.contains(&b64(SECRET)),
"precondition: the secret must NOT be 3-byte aligned inside the blob"
);
let script =
format!("echo '{blob}' | base64 -d > '/etc/app.conf'\nchmod '0600' '/etc/app.conf'");
let value_only = super::redact_secrets(&script, &[SECRET.to_string()]);
assert!(
value_only.contains(&blob),
"value-only redaction is expected to miss the blob; if it stopped \
missing it, this test no longer proves what it claims"
);
let out = redact_transcript(&script, &[SECRET.to_string()]);
assert!(!out.contains(&blob), "blob survived redaction:\n{out}");
assert!(out.contains("| base64 -d > '/etc/app.conf'"), "{out}");
assert!(out.contains("chmod '0600'"), "{out}");
}
#[test]
fn innocent_base64_survives() {
let blob = b64("nothing to see here at all");
let script = format!("echo '{blob}' | base64 -d");
let out = redact_transcript(&script, &[SECRET.to_string()]);
assert_eq!(out, script);
}
#[test]
fn no_secrets_is_the_identity() {
let script = "echo hello";
assert_eq!(redact_transcript(script, &[]), script);
}
#[test]
fn empty_secret_never_shreds_the_transcript() {
let script = "echo hello";
assert_eq!(redact_transcript(script, &[String::new()]), script);
}
fn file_secrets(dir: &std::path::Path, key: &str, value: &str) -> SecretsConfig {
std::fs::write(dir.join(key), value).unwrap();
SecretsConfig {
provider: Some("file".into()),
path: Some(dir.to_string_lossy().into_owned()),
..Default::default()
}
}
#[test]
fn collect_finds_the_secret_a_resource_interpolates() {
let tmp = tempfile::tempdir().unwrap();
let cfg = file_secrets(tmp.path(), "e04-unit", SECRET);
let r = Resource {
resource_type: ResourceType::File,
path: Some("/etc/app.conf".into()),
content: Some("api_token={{secrets.e04-unit}}\n".into()),
..Default::default()
};
assert_eq!(collect_secret_values(&r, &cfg), vec![SECRET.to_string()]);
}
#[test]
fn collect_finds_secrets_in_any_field() {
let tmp = tempfile::tempdir().unwrap();
let cfg = file_secrets(tmp.path(), "e04-cmd", SECRET);
let r = Resource {
resource_type: ResourceType::Task,
command: Some("curl -H 'Auth: {{secrets.e04-cmd}}' https://x".into()),
..Default::default()
};
assert_eq!(collect_secret_values(&r, &cfg), vec![SECRET.to_string()]);
}
#[test]
fn collect_deduplicates() {
let tmp = tempfile::tempdir().unwrap();
let cfg = file_secrets(tmp.path(), "e04-dup", SECRET);
let r = Resource {
resource_type: ResourceType::Task,
command: Some("{{secrets.e04-dup}} {{secrets.e04-dup}}".into()),
..Default::default()
};
assert_eq!(collect_secret_values(&r, &cfg), vec![SECRET.to_string()]);
}
#[test]
fn collect_skips_keys_that_do_not_resolve() {
let tmp = tempfile::tempdir().unwrap();
let cfg = file_secrets(tmp.path(), "unrelated", SECRET);
let r = Resource {
resource_type: ResourceType::File,
content: Some("{{secrets.e04-absent-key-nobody-set}}".into()),
..Default::default()
};
assert!(collect_secret_values(&r, &cfg).is_empty());
}
#[test]
fn collect_ignores_non_secret_templates() {
let r = Resource {
resource_type: ResourceType::File,
content: Some("{{params.env}} {{machine.web.addr}}".into()),
..Default::default()
};
assert!(collect_secret_values(&r, &SecretsConfig::default()).is_empty());
}
fn enc_marker() -> String {
format!("ENC[age,{}]", b64("age-ciphertext-stand-in-long-enough"))
}
#[test]
fn an_encrypted_value_cannot_be_named_by_the_collector() {
let r = Resource {
resource_type: ResourceType::File,
path: Some("/etc/app.conf".into()),
content: Some(format!("api_token={}\n", enc_marker())),
..Default::default()
};
assert!(
collect_secret_values(&r, &SecretsConfig::default()).is_empty(),
"if this ever returns a value, `carries_ciphertext` can stop suppressing"
);
}
#[test]
fn a_resource_carrying_ciphertext_is_recognised() {
let r = Resource {
resource_type: ResourceType::File,
content: Some(format!("api_token={}\n", enc_marker())),
..Default::default()
};
assert!(super::redaction::carries_ciphertext(&r));
}
#[test]
fn prose_about_enc_markers_is_not_ciphertext() {
let r = Resource {
resource_type: ResourceType::Task,
command: Some("echo 'values use ENC[age,...] markers'".into()),
..Default::default()
};
assert!(!super::redaction::carries_ciphertext(&r));
let plain = Resource {
resource_type: ResourceType::File,
content: Some("api_token=plain\n".into()),
..Default::default()
};
assert!(!super::redaction::carries_ciphertext(&plain));
}