mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Block unsafe git global options from safe allowlist (#15796)
## Summary - block git global options that can redirect config, repository, or helper lookup from being auto-approved as safe - share the unsafe global-option predicate across the Unix and Windows git safety checks - add regression coverage for inline and split forms, including `bash -lc` and PowerShell wrappers ## Root cause The Unix safe-command gate only rejected `-c` and `--config-env`, even though the shared git parser already knew how to skip additional pre-subcommand globals such as `--git-dir`, `--work-tree`, `--exec-path`, `--namespace`, and `--super-prefix`. That let those arguments slip through safe-command classification on otherwise read-only git invocations and bypass approval. The Windows-specific safe-command path had the same trust-boundary gap for git global options.
This commit is contained in:
@@ -4,6 +4,7 @@ use crate::command_safety::is_dangerous_command::executable_name_lookup_key;
|
||||
// may appear before it (e.g., `-C`, `-c`, `--git-dir`).
|
||||
// Implemented in `is_dangerous_command` and shared here.
|
||||
use crate::command_safety::is_dangerous_command::find_git_subcommand;
|
||||
use crate::command_safety::is_dangerous_command::git_global_option_requires_prompt;
|
||||
use crate::command_safety::windows_safe_commands::is_safe_command_windows;
|
||||
|
||||
pub fn is_known_safe_command(command: &[String]) -> bool {
|
||||
@@ -134,10 +135,10 @@ fn is_safe_to_call_with_exec(command: &[String]) -> bool {
|
||||
|
||||
// Git
|
||||
Some("git") => {
|
||||
// Global config overrides like `-c core.pager=...` can force git
|
||||
// to execute arbitrary external commands. With no sandboxing, we
|
||||
// should always prompt in those cases.
|
||||
if git_has_config_override_global_option(command) {
|
||||
// Global options that redirect config, repository, or helper
|
||||
// lookup can make otherwise read-only git commands execute
|
||||
// attacker-controlled code, so they must never be auto-approved.
|
||||
if git_has_unsafe_global_option(command) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -208,12 +209,12 @@ fn git_branch_is_read_only(branch_args: &[String]) -> bool {
|
||||
saw_read_only_flag
|
||||
}
|
||||
|
||||
fn git_has_config_override_global_option(command: &[String]) -> bool {
|
||||
command.iter().map(String::as_str).any(|arg| {
|
||||
matches!(arg, "-c" | "--config-env")
|
||||
|| (arg.starts_with("-c") && arg.len() > 2)
|
||||
|| arg.starts_with("--config-env=")
|
||||
})
|
||||
fn git_has_unsafe_global_option(command: &[String]) -> bool {
|
||||
command
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(String::as_str)
|
||||
.any(git_global_option_requires_prompt)
|
||||
}
|
||||
|
||||
fn git_subcommand_args_are_read_only(args: &[String]) -> bool {
|
||||
@@ -356,7 +357,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_output_and_config_override_flags_are_not_safe() {
|
||||
fn git_output_flags_are_not_safe() {
|
||||
assert!(!is_known_safe_command(&vec_str(&[
|
||||
"git",
|
||||
"log",
|
||||
@@ -376,6 +377,10 @@ mod tests {
|
||||
"--output=/tmp/git-show-out-test",
|
||||
"HEAD",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_global_override_flags_are_not_safe() {
|
||||
assert!(!is_known_safe_command(&vec_str(&[
|
||||
"git",
|
||||
"-c",
|
||||
@@ -389,6 +394,32 @@ mod tests {
|
||||
"-ccore.pager=cat",
|
||||
"status",
|
||||
])));
|
||||
|
||||
for args in [
|
||||
vec_str(&["git", "--config-env", "core.pager=PAGER", "show", "HEAD"]),
|
||||
vec_str(&["git", "--config-env=core.pager=PAGER", "show", "HEAD"]),
|
||||
vec_str(&["git", "--git-dir", ".evil-git", "diff", "HEAD~1..HEAD"]),
|
||||
vec_str(&["git", "--git-dir=.evil-git", "diff", "HEAD~1..HEAD"]),
|
||||
vec_str(&["git", "--work-tree", ".", "status"]),
|
||||
vec_str(&["git", "--work-tree=.", "status"]),
|
||||
vec_str(&["git", "--exec-path", ".git/helpers", "show", "HEAD"]),
|
||||
vec_str(&["git", "--exec-path=.git/helpers", "show", "HEAD"]),
|
||||
vec_str(&["git", "--namespace", "attacker", "show", "HEAD"]),
|
||||
vec_str(&["git", "--namespace=attacker", "show", "HEAD"]),
|
||||
vec_str(&["git", "--super-prefix", "attacker/", "show", "HEAD"]),
|
||||
vec_str(&["git", "--super-prefix=attacker/", "show", "HEAD"]),
|
||||
] {
|
||||
assert!(
|
||||
!is_known_safe_command(&args),
|
||||
"expected {args:?} to require approval due to unsafe git global option",
|
||||
);
|
||||
}
|
||||
|
||||
assert!(!is_known_safe_command(&vec_str(&[
|
||||
"bash",
|
||||
"-lc",
|
||||
"git --git-dir=.evil-git diff HEAD~1..HEAD",
|
||||
])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user