fix: greedy secrets regex caused multiple secrets on one line to fail

This commit is contained in:
2026-06-04 15:41:56 -06:00
parent e54a2e42c9
commit 8d6e9bef32
2 changed files with 40 additions and 19 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ use std::sync::{Arc, LazyLock};
use tokio::runtime::Handle; use tokio::runtime::Handle;
use uuid::Uuid; use uuid::Uuid;
pub static SECRET_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\{\{(.+)}}").unwrap()); pub static SECRET_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\{\{([^{}]+)}}").unwrap());
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct Vault { pub struct Vault {
+39 -18
View File
@@ -537,9 +537,48 @@ mod tests {
); );
} }
#[test]
fn interpolates_multiple_secrets_on_same_line() {
let calls = Calls::new();
let (out, missing) = interpolate_secrets_with("url={{URL}} key={{KEY}}", None, |name| {
calls.record(name);
match name {
"URL" => Ok("https://example.test".to_string()),
"KEY" => Ok("sk-12345".to_string()),
other => panic!("unexpected lookup: {other}"),
}
})
.unwrap();
assert_eq!(calls.snapshot(), vec!["URL".to_string(), "KEY".to_string()]);
assert_eq!(out, "url=https://example.test key=sk-12345");
assert!(missing.is_empty());
}
#[test]
fn regex_rejects_braces_in_secret_names() {
let calls = Calls::new();
let (out, missing) =
interpolate_secrets_with("literal {{ {NOT_A_NAME} }} text", None, |name| {
calls.record(name);
Ok(format!("got-{name}"))
})
.unwrap();
assert!(
calls.snapshot().is_empty(),
"name with embedded braces must not match"
);
assert_eq!(out, "literal {{ {NOT_A_NAME} }} text");
assert!(missing.is_empty());
}
#[test] #[test]
fn fatal_failure_short_circuits_remaining_lines() { fn fatal_failure_short_circuits_remaining_lines() {
let calls = Calls::new(); let calls = Calls::new();
let result = let result =
interpolate_secrets_with("a={{S1}}\nb={{S2}}\nc={{S3}}\nd={{S4}}", None, |name| { interpolate_secrets_with("a={{S1}}\nb={{S2}}\nc={{S3}}\nd={{S4}}", None, |name| {
calls.record(name); calls.record(name);
@@ -578,22 +617,4 @@ mod tests {
"expected hint contents, got: {err}" "expected hint contents, got: {err}"
); );
} }
#[test]
fn regex_greedy_capture_collapses_multi_secret_line_into_single_lookup() {
let calls = Calls::new();
let (out, missing) = interpolate_secrets_with("url={{URL}} key={{KEY}}", None, |name| {
calls.record(name);
Err(not_found(name))
})
.unwrap();
assert_eq!(
calls.snapshot(),
vec!["URL}} key={{KEY".to_string()],
"greedy regex spans first {{ to last }}, collapsing the whole line into one bogus lookup"
);
assert_eq!(out, "url=");
assert_eq!(missing, vec!["URL}} key={{KEY".to_string()]);
}
} }