diff --git a/src/lib.rs b/src/lib.rs index 8f77bcf..61bec1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -227,11 +227,15 @@ fn anonymize(text: &str, custom_raw: &str) -> Result<(String, Vec), M "IBAN", // Match the structural shape, surrounded by word // boundaries so we don't slice into longer alnum - // strings. + // strings. The BBAN body allows an OPTIONAL single space + // before each character so the standard grouped spelling + // (`DE89 3704 0044 0532 0130 00`) is redacted, not just + // the contiguous form — otherwise the leading group leaks + // and the phone rule mislabels the rest. Regex::new(r"(?x) \b [A-Z]{2}\d{2} - [A-Z0-9]{11,30} + (?:\s?[A-Z0-9]){11,30} \b ").map_err(re_err)?, ), @@ -434,6 +438,23 @@ mod tests { assert_eq!(reds.iter().filter(|r| r.kind == "BIC").count(), 1); } + #[test] + fn redacts_grouped_iban_without_phone_misclassification() { + // The standard German spelling groups the IBAN in 4-char + // blocks with spaces. It must redact as a single IBAN — not + // leak the leading group while the phone rule eats the rest. + let (out, reds) = + anonymize("Zahlung an DE89 3704 0044 0532 0130 00 heute", "").unwrap(); + assert!( + out.contains("\u{27E6}IBAN_1\u{27E7}"), + "grouped IBAN not redacted: {out}" + ); + assert!(!out.contains("PHONE"), "grouped IBAN mislabeled as phone: {out}"); + assert!(!out.contains("DE89"), "leading IBAN group leaked: {out}"); + assert_eq!(reds.iter().filter(|r| r.kind == "IBAN").count(), 1); + assert_eq!(reds.iter().filter(|r| r.kind == "PHONE").count(), 0); + } + #[test] fn redacts_phone_numbers() { let cases = [