use std::path::Path;
use super::super::destructive_delete::{DeleteTarget, evaluate_destructive_delete_command};
use super::super::{evaluate_bash_command, unclassifiable_command};
use super::*;
fn floor(command: &str) -> Option<DeleteTarget> {
evaluate_destructive_delete_command(command, Path::new("/nonexistent-9344"))
}
const PAYLOADS: &[&str] = &["cd ~; rm -rf .", "echo $(rm -rf ~)", "rm -rf ~"];
const INJECTED: &[&str] = &[
"X=$(true) git -c alias.x='!sh' x <<'O'",
"X=`true` git -c alias.x='!sh' x <<'O'",
"X=$(true) f <<'O'",
"X=`true` f <<'O'",
"X=$((1+2)) Y=$(echo a) f <<'O'",
"GIT_EXEC_PATH=$(echo /tmp/x) git commit -F - <<'O'",
"GIT_CONFIG_PARAMETERS=`printf x` git commit -F - <<'O'",
"X=$(true) git commit -F - <<'O'",
"X=$(true) tm x - <<'O'",
"X=$(GIT_EXEC_PATH=/tmp/x git commit -F - <<'O'",
"X=$(echo ')' cat) f <<'O'",
"X=$(echo \")\" cat) . /dev/stdin <<'O'",
"X=$(echo \\) cat) perl <<'O'",
"X=$(echo ')' cat) gh x <<'O'",
"git -c alias.x='!sh' x <<'O'",
"git -c alias.x=\\!sh x<<'O'",
"git -C /tmp -c alias.x='!sh' x <<'O'",
"git --config alias.x='!sh' x <<'O'",
"git --config-env=alias.x=SH x <<'O'",
"git --config-env alias.x=SH x <<'O'",
"git -c core.hooksPath=/tmp/h commit -F - <<'O'",
"git --exec-path=/tmp/x commit -F - <<'O'",
"GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=alias.x GIT_CONFIG_VALUE_0='!sh' git x <<'O'",
"GIT_CONFIG_PARAMETERS=\"'alias.x=!sh'\" git x <<'O'",
"git x <<'O'",
"gh x <<'O'",
"gh -R o/r x <<'O'",
"cat <<'O' | git -c alias.x='!sh' x",
];
#[test]
fn config_or_alias_injected_reader_bodies_reach_the_floor_9344() {
let mut allowed = Vec::new();
for line in INJECTED {
for payload in PAYLOADS {
let command = format!("{line}\n{payload}\nO");
if !floor(&command).is_some_and(DeleteTarget::is_floor) {
allowed.push(command);
}
}
}
assert!(allowed.is_empty(), "allowed: {allowed:#?}");
}
#[test]
fn injected_reader_lines_are_not_data_9344() {
for line in INJECTED {
assert!(!line_reads_as_data(line), "{line:?}");
}
for line in [
"git -c alias.x='!sh' x <<'O'",
"git --config-env=alias.x=SH x <<'O'",
"GIT_CONFIG_COUNT=1 git x <<'O'",
"GIT_EXEC_PATH=$(echo /tmp/x) git commit -F - <<'O'",
] {
assert!(line_runs_a_shell(line), "{line:?}");
}
}
#[test]
fn unclosed_quoted_value_substitutions_stay_data_9344() {
for line in [
"cat > f <<'EOF'",
"msg=$(cat <<'EOF'",
"msg=\"$(cat <<'EOF'",
"git commit -m \"$(cat <<'EOF'",
"gh pr create --body-file - <<'EOF'",
] {
assert!(line_reads_as_data(line), "{line:?}");
}
assert!(!line_reads_as_data(
"X=$(git rev-parse HEAD) git commit -F - <<'O'"
));
}
#[test]
fn a_data_reader_body_holding_a_root_delete_is_denied_9344() {
for command in [
"git commit -F - <<'EOF'\nrm -rf /\nEOF",
"git commit -F - <<'EOF'\nfix: thing\n\n sudo rm -rf ~\nEOF",
"gh pr create --body-file - <<'EOF'\nok; rm -rf $HOME\nEOF",
"gh issue comment 1 --body-file - <<'EOF'\nfind / -delete\nEOF",
"cat > s.sh <<'EOF'\nrm -rf /\nEOF",
"tee s.sh <<'EOF'\nrm -fr ~/\nEOF",
"bash <<'O'\ncat > s.sh <<'E'\nrm -rf /\nE\nO",
"cat > f <<'EOF'\nsudo --frobnicate rm -rf /\nEOF",
"cat > f <<'EOF'\ntimeout soon rm -rf /\nEOF",
] {
assert!(
floor(command).is_some_and(DeleteTarget::is_floor),
"{command:?}"
);
}
}
#[test]
fn commit_and_pr_body_shapes_stay_allowed_9344() {
for command in [
"git commit -F - <<'EOF'\nfix: rm stale files\n\nThe floor denies `rm -rf /` and `rm -rf ~`.\nEOF",
"git commit -F - <<'EOF'\nfix: make build passes; rm stale.log first\n\nRefs #9344\nEOF",
"gh pr create --title t --body-file - <<'EOF'\n## Summary\nDo not rm the cache; run `rm -rf /` never.\nEOF",
"gh issue comment 9344 --body-file - <<'EOF'\nWe rm old logs; the guard denies `rm -rf ~` in a body.\nEOF",
"git -C /tmp/repo commit -F - <<'EOF'\nfix: thing\nEOF",
"gh api repos/o/r/issues --input - <<'EOF'\n{\"body\": \"rm the cache\"}\nEOF",
] {
assert_eq!(unclassifiable_command(command), None, "{command:?}");
assert_eq!(evaluate_bash_command(command), None, "{command:?}");
assert_eq!(floor(command), None, "{command:?}");
}
}