harden: validate package-name charset in request_builder

`request_builder` interpolates `package` verbatim into a device-shell
action string. Every current caller reconciles the name against the live
device package list first, so this is safe today — but the safety lives in
the callers, not the sink. Add a local charset guard (Android app-ID set
`[A-Za-z0-9_.]`) so a future caller passing an unreconciled or file-sourced
name can't produce an injectable device-shell command. Rejects nothing
legitimate; fails closed (emits no command) on a malformed name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
aeonframework 2026-06-22 10:54:06 -04:00
commit bd1ed7ea76

View file

@ -206,6 +206,22 @@ pub fn apply_pkg_state_commands(
/// which act on a common `package` and `user`.
#[must_use]
pub fn request_builder(commands: &[&str], package: &str, user: Option<User>) -> Vec<String> {
// Defense-in-depth: `package` is interpolated verbatim into a device-shell
// action, so refuse any name carrying a character that can't appear in a valid
// Android application-ID (`[A-Za-z0-9_.]`). Every current caller already
// reconciles the name against the live device package list before reaching
// here, so this rejects nothing legitimate — it just keeps the no-injection
// guarantee local to the sink instead of relying on each caller to sanitise.
// Fail closed: emit no command for a malformed name rather than an injectable
// device-shell string.
if package.is_empty()
|| !package
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'.' || b == b'_')
{
error!("request_builder: refusing package name with invalid characters: {package:?}");
return Vec::new();
}
let maybe_user_flag = user_flag(user);
commands
.iter()