fix(tauri): strip cmd.exe metacharacters from launch usernames
Security audit finding SEC-IPC-01 (parameter part). The username is passed to game_setup/game_start/server_start batch scripts as a quoted `cmd.exe` argument. Quoting protects the launcher's own command line, but batch scripts expand `%~4` textually into their own statements, so a name such as `foo & calc` would run `calc` from `set NAME=%~4`. Since the setup script runs elevated, that matters even though the value is the local user's own input. `sanitize_username` previously removed control characters, `"` and `%`; it now also removes `& | < > ^`. Spaces, punctuation such as `!` and non-ASCII letters remain allowed so ordinary gamer tags are not mangled. The audit's stricter `[A-Za-z0-9_-]` allowlist was rejected for that reason. The elevated execution of catalog scripts itself is intentional: the shared games need administrator setup and the archives that carry the scripts are BLAKE3-verified against the bundled catalog. Test plan: `just test` (extended sanitizer test). Claude-Session: https://claude.ai/code/session_017C3Nbgwpdm3YNwZhhFLHwg
This commit is contained in:
@@ -1470,12 +1470,18 @@ fn sanitize_language(language: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Characters that `cmd.exe` interprets even when the argument was quoted,
|
||||||
|
/// because batch scripts expand `%~4` textually into their own command lines.
|
||||||
|
/// `"` would end the quoted argument and `%` starts variable expansion; the
|
||||||
|
/// rest are command separators, redirection and the escape character.
|
||||||
|
const CMD_UNSAFE_USERNAME_CHARS: [char; 7] = ['"', '%', '&', '|', '<', '>', '^'];
|
||||||
|
|
||||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||||
fn sanitize_username(username: &str) -> String {
|
fn sanitize_username(username: &str) -> String {
|
||||||
let cleaned = username
|
let cleaned = username
|
||||||
.trim()
|
.trim()
|
||||||
.chars()
|
.chars()
|
||||||
.filter(|c| !c.is_control() && *c != '"' && *c != '%')
|
.filter(|c| !c.is_control() && !CMD_UNSAFE_USERNAME_CHARS.contains(c))
|
||||||
.take(MAX_USERNAME_CHARS)
|
.take(MAX_USERNAME_CHARS)
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
@@ -6173,6 +6179,15 @@ mod tests {
|
|||||||
username: DEFAULT_USERNAME.to_string(),
|
username: DEFAULT_USERNAME.to_string(),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
// cmd.exe metacharacters are stripped even inside a quoted argument;
|
||||||
|
// everything else, including spaces and non-ASCII letters, survives.
|
||||||
|
assert_eq!(
|
||||||
|
launch_settings("en", "Jörg & Co | x > y < z ^ !"),
|
||||||
|
LaunchSettings {
|
||||||
|
language: "en".to_string(),
|
||||||
|
username: "Jörg Co x y z !".to_string(),
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user