Skip to content

Commit d679401

Browse files
fix(auth): prevent mask_secret panic on multi-byte UTF-8 secrets (#591)
Use char-based indexing instead of byte-offset slicing in mask_secret() to prevent panics when secrets contain multi-byte UTF-8 characters (accented letters, emoji, CJK, etc.). Co-authored-by: jpoehnelt-bot <[email protected]>
1 parent 01fb4f2 commit d679401

File tree

2 files changed

+23
-6
lines changed

2 files changed

+23
-6
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@googleworkspace/cli": patch
3+
---
4+
5+
Fix `mask_secret` panic on multi-byte UTF-8 secrets by using char-based indexing instead of byte-offset slicing

src/auth_commands.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,19 @@ use crate::error::GwsError;
2222

2323
/// Mask a secret string by showing only the first 4 and last 4 characters.
2424
/// Strings with 8 or fewer characters are fully replaced with "***".
25+
///
26+
/// Uses char-based indexing (not byte offsets) so multi-byte UTF-8 secrets
27+
/// never cause a panic.
2528
fn mask_secret(s: &str) -> String {
2629
const MASK_PREFIX_LEN: usize = 4;
2730
const MASK_SUFFIX_LEN: usize = 4;
2831
const MIN_LEN_FOR_PARTIAL_MASK: usize = MASK_PREFIX_LEN + MASK_SUFFIX_LEN;
2932

30-
if s.len() > MIN_LEN_FOR_PARTIAL_MASK {
31-
format!(
32-
"{}...{}",
33-
&s[..MASK_PREFIX_LEN],
34-
&s[s.len() - MASK_SUFFIX_LEN..]
35-
)
33+
let char_count = s.chars().count();
34+
if char_count > MIN_LEN_FOR_PARTIAL_MASK {
35+
let prefix: String = s.chars().take(MASK_PREFIX_LEN).collect();
36+
let suffix: String = s.chars().skip(char_count - MASK_SUFFIX_LEN).collect();
37+
format!("{prefix}...{suffix}")
3638
} else {
3739
"***".to_string()
3840
}
@@ -2124,6 +2126,16 @@ mod tests {
21242126
assert_eq!(mask_secret("123456789"), "1234...6789");
21252127
}
21262128

2129+
#[test]
2130+
fn mask_secret_multibyte_utf8() {
2131+
// Multi-byte chars must not panic (previously used byte slicing)
2132+
assert_eq!(mask_secret("áéíóúñüÁÉÍÓÚ"), "áéíó...ÉÍÓÚ");
2133+
// Short multi-byte — should fully mask
2134+
assert_eq!(mask_secret("café"), "***");
2135+
// Exactly at boundary with multi-byte (9 Greek chars)
2136+
assert_eq!(mask_secret("αβγδεζηθι"), "αβγδ...ζηθι");
2137+
}
2138+
21272139
#[test]
21282140
fn find_unmatched_services_identifies_missing() {
21292141
let scopes = vec![

0 commit comments

Comments
 (0)