diff --git a/crates/tinyinference-core/src/sanitize.rs b/crates/tinyinference-core/src/sanitize.rs index d52c273..660df06 100644 --- a/crates/tinyinference-core/src/sanitize.rs +++ b/crates/tinyinference-core/src/sanitize.rs @@ -336,8 +336,9 @@ pub fn scrub_credentials(input: &str) -> String { return full_match.to_string(); } // Already redacted: an unquoted value stops at `*`, so a second pass - // would match the kept prefix and stack another marker. Leave it. - if rest.starts_with("*[REDACTED]") { + // would match the kept prefix and stack another marker. A short + // quoted value was replaced whole, so its value is the marker. + if rest.starts_with("*[REDACTED]") || value.as_str().starts_with("*[REDACTED]") { return full_match.to_string(); } // Replace only the value span. Rebuilding the key from captures used diff --git a/crates/tinyinference-core/src/sanitize_tests.rs b/crates/tinyinference-core/src/sanitize_tests.rs index d70530c..9733bd6 100644 --- a/crates/tinyinference-core/src/sanitize_tests.rs +++ b/crates/tinyinference-core/src/sanitize_tests.rs @@ -137,6 +137,15 @@ fn scrub_credentials_is_idempotent() { assert_eq!(scrub_credentials(&once), once); } +// A second pass must not stack a marker on a short value that the first +// pass replaced whole (`"ab"` -> `"*[REDACTED]"`). +#[test] +fn short_quoted_value_scrubs_idempotently() { + let once = scrub_credentials(r#"{"token":"ab"}"#); + assert_eq!(once, r#"{"token":"*[REDACTED]"}"#); + assert_eq!(scrub_credentials(&once), once); +} + #[test] fn scrub_credentials_empty_input() { assert_eq!(scrub_credentials(""), "");