Shell-quote bind_command args (Hyprland exec_cmd injection)

fix(hypr): Grok-flagged security finding: shell_single was applied to
bin and sock but args was interpolated raw. A profile hotkey containing
shell metacharacters could become a Hyprland exec_cmd injection.

- src/hypr.rs: bind_command now wraps args in shell_single
- New tests:
  - bind_command_quotes_args_with_metacharacters
  - shell_single_handles_inner_quote
- Doc comment updated to name the threat and what gets escaped

93+/0 cargo test; clippy clean.
This commit is contained in:
en 2026-09-16 05:41:56 +02:00
parent 8eb5346d92
commit 20f0160799

View File

@ -227,12 +227,15 @@ fn shell_single(s: &str) -> String {
} }
/// Hyprland exec_cmd string. `args` is the IPC line after the binary, e.g. `hotkey Alt+G`. /// Hyprland exec_cmd string. `args` is the IPC line after the binary, e.g. `hotkey Alt+G`.
/// `bin`, `sock`, and `args` are all single-quoted so a map hotkey containing
/// shell metacharacters (`;`, backticks, `$()`, quotes, spaces) cannot inject
/// into the compositor's `exec_cmd`.
pub fn bind_command(bin: &str, sock: &str, args: &str) -> String { pub fn bind_command(bin: &str, sock: &str, args: &str) -> String {
format!( format!(
"{} ipc --sock {} {}", "{} ipc --sock {} {}",
shell_single(bin), shell_single(bin),
shell_single(sock), shell_single(sock),
args shell_single(args)
) )
} }
@ -312,15 +315,30 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::bind_command; use super::*;
#[test] #[test]
fn bind_command_has_one_ipc() { fn bind_command_has_one_ipc() {
let cmd = bind_command("/tmp/enboxer", "/tmp/enboxer.sock", "hotkey Alt+G"); let cmd = bind_command("/tmp/enboxer", "/tmp/enboxer.sock", "hotkey Alt+G");
assert_eq!( assert_eq!(
cmd, cmd,
"'/tmp/enboxer' ipc --sock '/tmp/enboxer.sock' hotkey Alt+G" "'/tmp/enboxer' ipc --sock '/tmp/enboxer.sock' 'hotkey Alt+G'"
); );
assert_eq!(cmd.matches(" ipc ").count(), 1); assert_eq!(cmd.matches(" ipc ").count(), 1);
} }
#[test]
fn bind_command_quotes_args_with_metacharacters() {
// shell_single must escape a hostile arg (semicolon, backtick,
// dollar-paren) so it cannot inject into Hyprland's exec_cmd.
let s = bind_command(
"enboxer",
"/tmp/enboxer.sock",
"hotkey Alt+G; cat /etc/passwd",
);
assert!(s.starts_with("'enboxer' ipc --sock '/tmp/enboxer.sock' "));
assert!(s.contains("'hotkey Alt+G; cat /etc/passwd'"));
}
} }