diff --git a/crates/app-server/src/lib.rs b/crates/app-server/src/lib.rs index bb5d7dcf..40fcca49 100644 --- a/crates/app-server/src/lib.rs +++ b/crates/app-server/src/lib.rs @@ -284,7 +284,7 @@ async fn tool_handler( .as_deref() .and_then(|p| match p.trim().to_ascii_lowercase().as_str() { "auto" | "yolo" => Some(codewhale_execpolicy::AskForApproval::UnlessTrusted), - "never" | "deny" => Some(codewhale_execpolicy::AskForApproval::OnRequest), + "never" | "deny" => Some(codewhale_execpolicy::AskForApproval::Never), _ => None, }) .unwrap_or(codewhale_execpolicy::AskForApproval::OnRequest) diff --git a/crates/execpolicy/src/bash_arity.rs b/crates/execpolicy/src/bash_arity.rs index 467c1640..e87afa30 100644 --- a/crates/execpolicy/src/bash_arity.rs +++ b/crates/execpolicy/src/bash_arity.rs @@ -373,7 +373,8 @@ impl BashArityDict { .collect::>() .join(" "); command_norm == pattern_norm - || command_norm.starts_with(&format!("{pattern_norm} ")) + || (command_norm.starts_with(&pattern_norm) + && command_norm.as_bytes().get(pattern_norm.len()) == Some(&b' ')) } /// Iterate over all entries in the dictionary. diff --git a/crates/execpolicy/src/lib.rs b/crates/execpolicy/src/lib.rs index 05ed7a4e..2d7b0b02 100644 --- a/crates/execpolicy/src/lib.rs +++ b/crates/execpolicy/src/lib.rs @@ -352,7 +352,9 @@ impl ExecPolicyEngine { // blocks "rm -rf /" but NOT "rmdir" or "rmview". if let Some(rule) = denied_prefixes.iter().find(|rule| { let norm_rule = normalize_command(rule); - normalized == norm_rule || normalized.starts_with(&format!("{norm_rule} ")) + normalized == norm_rule + || (normalized.starts_with(&norm_rule) + && normalized.as_bytes().get(norm_rule.len()) == Some(&b' ')) }) { return Ok(ExecPolicyDecision { allow: false, @@ -444,7 +446,12 @@ impl ExecPolicyEngine { } fn normalize_command(value: &str) -> String { - value.trim().to_ascii_lowercase() + // Normalize: lowercase, collapse internal whitespace to single spaces. + // This prevents bypass via "git status" (double space) vs "git status". + value.split_whitespace() + .collect::>() + .join(" ") + .to_ascii_lowercase() } fn first_token(command: &str) -> String {