mirror of
https://github.com/openai/codex.git
synced 2026-05-03 12:52:11 +03:00
Remove git commands from dangerous command checks (#11510)
### Motivation - Git subcommand matching was being classified as "dangerous" and caused benign developer workflows (for example `git push --force-with-lease`) to be blocked by the preflight policy. - The change aligns behavior with the intent to reserve the dangerous checklist for truly destructive shell ops (e.g. `rm -rf`) and avoid surprising developer-facing blocks. ### Description - Remove git-specific subcommand checks from `is_dangerous_to_call_with_exec` in `codex-rs/shell-command/src/command_safety/is_dangerous_command.rs`, leaving only explicit `rm` and `sudo` passthrough checks. - Deleted the git-specific helper logic that classified `reset`, `branch`-delete, `push` (force/delete/refspec) and `clean --force` as dangerous. - Updated unit tests in the same file to assert that various `git reset`/`git branch`/`git push`/`git clean` variants are no longer classified as dangerous. - Kept `find_git_subcommand` (used by safe-command classification) intact so safe/unsafe parsing elsewhere remains functional. ### Testing - Ran formatter with `just fmt` successfully. - Ran unit tests with `cargo test -p codex-shell-command` and all tests passed (`144 passed; 0 failed`). ------ [Codex Task](https://chatgpt.com/codex/tasks/task_i_698d19dedb4883299c3ceb5bbc6a0dcf)
This commit is contained in:
@@ -104,25 +104,6 @@ fn is_dangerous_to_call_with_exec(command: &[String]) -> bool {
|
||||
let cmd0 = command.first().map(String::as_str);
|
||||
|
||||
match cmd0 {
|
||||
Some(cmd) if cmd.ends_with("git") => {
|
||||
let Some((subcommand_idx, subcommand)) =
|
||||
find_git_subcommand(command, &["reset", "rm", "branch", "push", "clean"])
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
match subcommand {
|
||||
"reset" | "rm" => true,
|
||||
"branch" => git_branch_is_delete(&command[subcommand_idx + 1..]),
|
||||
"push" => git_push_is_dangerous(&command[subcommand_idx + 1..]),
|
||||
"clean" => git_clean_is_force(&command[subcommand_idx + 1..]),
|
||||
other => {
|
||||
debug_assert!(false, "unexpected git subcommand from matcher: {other}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some("rm") => matches!(command.get(1).map(String::as_str), Some("-f" | "-rf")),
|
||||
|
||||
// for sudo <cmd> simply do the check for <cmd>
|
||||
@@ -133,48 +114,6 @@ fn is_dangerous_to_call_with_exec(command: &[String]) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn git_branch_is_delete(branch_args: &[String]) -> bool {
|
||||
// Git allows stacking short flags (for example, `-dv` or `-vd`). Treat any
|
||||
// short-flag group containing `d`/`D` as a delete flag.
|
||||
branch_args.iter().map(String::as_str).any(|arg| {
|
||||
matches!(arg, "-d" | "-D" | "--delete")
|
||||
|| arg.starts_with("--delete=")
|
||||
|| short_flag_group_contains(arg, 'd')
|
||||
|| short_flag_group_contains(arg, 'D')
|
||||
})
|
||||
}
|
||||
|
||||
fn short_flag_group_contains(arg: &str, target: char) -> bool {
|
||||
arg.starts_with('-') && !arg.starts_with("--") && arg.chars().skip(1).any(|c| c == target)
|
||||
}
|
||||
|
||||
fn git_push_is_dangerous(push_args: &[String]) -> bool {
|
||||
push_args.iter().map(String::as_str).any(|arg| {
|
||||
matches!(
|
||||
arg,
|
||||
"--force" | "--force-with-lease" | "--force-if-includes" | "--delete" | "-f" | "-d"
|
||||
) || arg.starts_with("--force-with-lease=")
|
||||
|| arg.starts_with("--force-if-includes=")
|
||||
|| arg.starts_with("--delete=")
|
||||
|| short_flag_group_contains(arg, 'f')
|
||||
|| short_flag_group_contains(arg, 'd')
|
||||
|| git_push_refspec_is_dangerous(arg)
|
||||
})
|
||||
}
|
||||
|
||||
fn git_push_refspec_is_dangerous(arg: &str) -> bool {
|
||||
// `+<refspec>` forces updates and `:<dst>` deletes remote refs.
|
||||
(arg.starts_with('+') || arg.starts_with(':')) && arg.len() > 1
|
||||
}
|
||||
|
||||
fn git_clean_is_force(clean_args: &[String]) -> bool {
|
||||
clean_args.iter().map(String::as_str).any(|arg| {
|
||||
matches!(arg, "--force" | "-f")
|
||||
|| arg.starts_with("--force=")
|
||||
|| short_flag_group_contains(arg, 'f')
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -183,193 +122,6 @@ mod tests {
|
||||
items.iter().map(std::string::ToString::to_string).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_reset_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&["git", "reset"])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bash_git_reset_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"bash",
|
||||
"-lc",
|
||||
"git reset --hard",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zsh_git_reset_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"zsh",
|
||||
"-lc",
|
||||
"git reset --hard",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_status_is_not_dangerous() {
|
||||
assert!(!command_might_be_dangerous(&vec_str(&["git", "status"])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bash_git_status_is_not_dangerous() {
|
||||
assert!(!command_might_be_dangerous(&vec_str(&[
|
||||
"bash",
|
||||
"-lc",
|
||||
"git status",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sudo_git_reset_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"sudo", "git", "reset", "--hard",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usr_bin_git_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"/usr/bin/git",
|
||||
"reset",
|
||||
"--hard",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_branch_delete_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "branch", "-d", "feature",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "branch", "-D", "feature",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"bash",
|
||||
"-lc",
|
||||
"git branch --delete feature",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_branch_delete_with_stacked_short_flags_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "branch", "-dv", "feature",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "branch", "-vd", "feature",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "branch", "-vD", "feature",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "branch", "-Dvv", "feature",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_branch_delete_with_global_options_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "-C", ".", "branch", "-d", "feature",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git",
|
||||
"-c",
|
||||
"color.ui=false",
|
||||
"branch",
|
||||
"-D",
|
||||
"feature",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"bash",
|
||||
"-lc",
|
||||
"git -C . branch -d feature",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_checkout_reset_is_not_dangerous() {
|
||||
// The first non-option token is "checkout", so later positional args
|
||||
// like branch names must not be treated as subcommands.
|
||||
assert!(!command_might_be_dangerous(&vec_str(&[
|
||||
"git", "checkout", "reset",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_push_force_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "push", "--force", "origin", "main",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "push", "-f", "origin", "main",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git",
|
||||
"-C",
|
||||
".",
|
||||
"push",
|
||||
"--force-with-lease",
|
||||
"origin",
|
||||
"main",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_push_plus_refspec_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "push", "origin", "+main",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git",
|
||||
"push",
|
||||
"origin",
|
||||
"+refs/heads/main:refs/heads/main",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_push_delete_flag_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "push", "--delete", "origin", "feature",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "push", "-d", "origin", "feature",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_push_delete_refspec_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "push", "origin", ":feature",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"bash",
|
||||
"-lc",
|
||||
"git push origin :feature",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_push_without_force_is_not_dangerous() {
|
||||
assert!(!command_might_be_dangerous(&vec_str(&[
|
||||
"git", "push", "origin", "main",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_clean_force_is_dangerous_even_when_f_is_not_first_flag() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "clean", "-fdx",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "clean", "-xdf",
|
||||
])));
|
||||
assert!(command_might_be_dangerous(&vec_str(&[
|
||||
"git", "clean", "--force",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rm_rf_is_dangerous() {
|
||||
assert!(command_might_be_dangerous(&vec_str(&["rm", "-rf", "/"])));
|
||||
|
||||
Reference in New Issue
Block a user