From 61d6fb723db5ac100375b03bf97fac1ba1ce9a76 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sun, 19 Apr 2026 15:04:13 -0400 Subject: [PATCH] fix(core): reject non-ASCII SymbolCharset::Custom at generate time Avoids from_utf8 panic when Custom contains multi-byte UTF-8 chars whose individual bytes are independently sampled into the output. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/idfoto-core/src/generators.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/idfoto-core/src/generators.rs b/crates/idfoto-core/src/generators.rs index b40e230..671583d 100644 --- a/crates/idfoto-core/src/generators.rs +++ b/crates/idfoto-core/src/generators.rs @@ -41,7 +41,14 @@ fn random_password( let symbols: &[u8] = match symbol_charset { SymbolCharset::SafeOnly => SAFE_SYMBOLS, SymbolCharset::Extended => EXTENDED_SYMBOLS, - SymbolCharset::Custom(s) => s.as_bytes(), + SymbolCharset::Custom(s) => { + if !s.is_ascii() { + return Err(IdfotoError::Format( + "SymbolCharset::Custom must be ASCII-only".into(), + )); + } + s.as_bytes() + } }; charset.extend_from_slice(symbols); } @@ -110,4 +117,15 @@ mod tests { "safe charset must not include {c}"); } } + + #[test] + fn custom_charset_rejects_non_ascii() { + let req = GeneratorRequest::Random { + length: 8, + classes: CharClasses { lower: false, upper: false, digits: false, symbols: true }, + symbol_charset: SymbolCharset::Custom("ñé".into()), + }; + let err = generate_password(&req); + assert!(err.is_err(), "non-ASCII custom charset must be rejected"); + } }