use ggen_utils::safe_command::{CommandArg, CommandName, SafeCommand};
use ggen_utils::safe_path::SafePath;
#[test]
fn test_command_injection_semicolon() {
let attacks = vec![
"build; rm -rf /",
"build ; rm -rf /",
"build ; rm -rf /",
"; rm -rf /",
];
for attack in attacks {
let result = SafeCommand::new("cargo").unwrap().arg(attack);
assert!(
result.is_err(),
"Should block semicolon injection: {}",
attack
);
assert!(
result.unwrap_err().to_string().contains("metacharacter"),
"Error should mention metacharacter"
);
}
}
#[test]
fn test_command_injection_pipe() {
let attacks = vec![
"build | tee output",
"build|tee output",
"build || echo hacked",
"| cat /etc/passwd",
];
for attack in attacks {
let result = SafeCommand::new("cargo").unwrap().arg(attack);
assert!(result.is_err(), "Should block pipe injection: {}", attack);
}
}
#[test]
fn test_command_injection_ampersand() {
let attacks = vec![
"build & rm -rf /",
"build&rm -rf /",
"build && rm -rf /",
"build&&rm -rf /",
"& rm -rf /",
];
for attack in attacks {
let result = SafeCommand::new("cargo").unwrap().arg(attack);
assert!(
result.is_err(),
"Should block ampersand injection: {}",
attack
);
}
}
#[test]
fn test_command_injection_redirection() {
let attacks = vec![
"build > /etc/passwd",
"build>> /etc/passwd",
"build < /etc/passwd",
"build 2>&1",
"> /etc/passwd",
"< /etc/passwd",
];
for attack in attacks {
let result = SafeCommand::new("cargo").unwrap().arg(attack);
assert!(
result.is_err(),
"Should block redirection injection: {}",
attack
);
}
}
#[test]
fn test_command_injection_command_substitution() {
let attacks = vec![
"$(whoami)",
"$(rm -rf /)",
"`whoami`",
"`rm -rf /`",
"build $(whoami)",
"build `whoami`",
];
for attack in attacks {
let result = SafeCommand::new("cargo").unwrap().arg(attack);
assert!(
result.is_err(),
"Should block command substitution: {}",
attack
);
}
}
#[test]
fn test_command_injection_newline() {
let attacks = vec![
"build\nrm -rf /",
"build\n\nrm -rf /",
"\nrm -rf /",
"build\rrm -rf /",
"build\r\nrm -rf /",
];
for attack in attacks {
let result = SafeCommand::new("cargo").unwrap().arg(attack);
assert!(
result.is_err(),
"Should block newline injection: {:?}",
attack
);
}
}
#[test]
fn test_whitelist_dangerous_commands() {
let dangerous = vec![
"rm", "rmdir", "dd", "mkfs", "kill", "killall", "pkill", "sudo", "su", "chmod", "chown",
"curl", "wget", "nc", "netcat", "telnet", "ssh", "scp", "rsync", "tar", "zip", "unzip",
"7z",
];
for cmd in dangerous {
let result = SafeCommand::new(cmd);
assert!(result.is_err(), "Should block dangerous command: {}", cmd);
assert!(
result.unwrap_err().to_string().contains("not in whitelist"),
"Error should mention whitelist for: {}",
cmd
);
}
}
#[test]
fn test_whitelist_case_sensitivity() {
let uppercase_attempts = vec!["CARGO", "GIT", "NPM", "Cargo", "Git", "Npm"];
for cmd in uppercase_attempts {
let result = SafeCommand::new(cmd);
assert!(result.is_err(), "Should be case-sensitive, block: {}", cmd);
}
}
#[test]
fn test_whitelist_with_path() {
let path_attempts = vec![
"/usr/bin/cargo",
"/bin/git",
"./cargo",
"../cargo",
"~/cargo",
];
for cmd in path_attempts {
let result = SafeCommand::new(cmd);
assert!(
result.is_err(),
"Should block path-qualified commands: {}",
cmd
);
}
}
#[test]
fn test_whitelist_with_whitespace() {
let whitespace_attempts = vec![
"cargo ", " cargo", " cargo ", "car go", "cargo\t", "\tcargo",
];
for cmd in whitespace_attempts {
let result = CommandName::new(cmd);
assert!(
result.is_err(),
"Should block command with whitespace: {:?}",
cmd
);
}
}
#[test]
fn test_path_argument_safe_path_validated() {
let safe_path = SafePath::new("src/generated").unwrap();
let cmd = SafeCommand::new("cargo")
.unwrap()
.arg_path(&safe_path)
.validate();
assert!(cmd.is_ok(), "SafePath arguments should be validated");
}
#[test]
fn test_path_argument_parent_dir_blocked() {
let attacks = vec![
"../../../etc/passwd",
"../../etc/passwd",
"../etc/passwd",
"subdir/../../etc/passwd",
];
for attack in attacks {
let result = SafePath::new(attack);
assert!(
result.is_err(),
"SafePath should block path traversal: {}",
attack
);
}
}
#[test]
fn test_safe_path_integration() {
let valid_path = SafePath::new("src/main.rs").unwrap();
let invalid_path_str = "../../../etc/passwd";
let valid_cmd = SafeCommand::new("rustfmt")
.unwrap()
.arg_path(&valid_path)
.validate();
let invalid_path = SafePath::new(invalid_path_str);
assert!(valid_cmd.is_ok(), "Valid SafePath should work");
assert!(invalid_path.is_err(), "Invalid path should be blocked");
}
#[test]
fn test_max_length_single_arg() {
let long_arg = "a".repeat(5000);
let result = SafeCommand::new("cargo")
.unwrap()
.arg(&long_arg)
.unwrap()
.validate();
assert!(result.is_err(), "Should block command exceeding max length");
assert!(
result.unwrap_err().to_string().contains("exceeds maximum"),
"Error should mention max length"
);
}
#[test]
fn test_max_length_many_args() {
let mut cmd = SafeCommand::new("cargo").unwrap();
for i in 0..500 {
cmd = cmd.arg(format!("arg_{:05}", i)).unwrap();
}
let result = cmd.validate();
assert!(result.is_err(), "Should block total length exceeding max");
}
#[test]
fn test_max_length_boundary() {
let boundary_arg = "a".repeat(4084);
let result = SafeCommand::new("cargo")
.unwrap()
.arg("build")
.unwrap()
.arg(&boundary_arg)
.unwrap()
.validate();
assert!(
result.is_ok(),
"Should allow command at max length boundary"
);
}
#[test]
fn test_max_length_just_over_boundary() {
let over_boundary_arg = "a".repeat(4086);
let result = SafeCommand::new("cargo")
.unwrap()
.arg("build")
.unwrap()
.arg(&over_boundary_arg)
.unwrap()
.validate();
assert!(result.is_err(), "Should block command just over max length");
}
#[test]
fn test_combined_attack_injection_and_path() {
let combined_attacks = vec![
"../../etc/passwd; rm -rf /",
"../../../etc/passwd | cat",
"../../etc/passwd && whoami",
];
for attack in combined_attacks {
let result = SafeCommand::new("cargo").unwrap().arg(attack);
assert!(result.is_err(), "Should block combined attack: {}", attack);
}
}
#[test]
fn test_combined_attack_multiple_stages() {
let result1 = SafeCommand::new("cargo")
.unwrap()
.arg("build")
.unwrap()
.arg("--release");
let result2 = result1.unwrap().arg("; rm -rf /");
assert!(result2.is_err(), "Should block injection in any arg");
}
#[test]
fn test_unicode_shell_metacharacters() {
let unicode_attacks = vec![
"build|cat /etc/passwd", "build;rm -rf /", "build&rm -rf /", ];
for attack in unicode_attacks {
let result = CommandArg::new(attack);
let _ = result;
}
}
#[test]
fn test_null_byte_in_command() {
let null_attacks = vec!["cargo\0", "\0cargo", "car\0go"];
for attack in null_attacks {
let result = CommandName::new(attack);
assert!(
result.is_err(),
"Should block command with null byte: {:?}",
attack
);
}
}
#[test]
fn test_null_byte_in_arg() {
let null_attacks = vec!["build\0", "\0", "build\0--release"];
for attack in null_attacks {
let result = CommandArg::new(attack);
let _ = result;
}
}
#[test]
fn test_args_bulk_all_valid() {
let args = vec!["build", "--release", "--all-features"];
let result = SafeCommand::new("cargo").unwrap().args(&args);
assert!(result.is_ok(), "All valid args should pass");
}
#[test]
fn test_args_bulk_one_invalid() {
let args = vec!["build", "--release; rm -rf /", "--all-features"];
let result = SafeCommand::new("cargo").unwrap().args(&args);
assert!(result.is_err(), "Should fail on first invalid arg");
}
#[test]
fn test_args_bulk_empty_vec() {
let args: Vec<&str> = vec![];
let result = SafeCommand::new("cargo").unwrap().args(&args);
assert!(result.is_ok(), "Empty args vec should be allowed");
}
#[test]
fn test_cargo_make_command() {
let cmd = SafeCommand::new("cargo")
.unwrap()
.arg("make")
.unwrap()
.arg("test")
.unwrap()
.validate();
assert!(cmd.is_ok());
assert_eq!(cmd.unwrap().to_string_debug(), "cargo make test");
}
#[test]
fn test_git_status_command() {
let cmd = SafeCommand::new("git")
.unwrap()
.arg("status")
.unwrap()
.validate();
assert!(cmd.is_ok());
assert_eq!(cmd.unwrap().to_string_debug(), "git status");
}
#[test]
fn test_timeout_wrapper() {
let cmd = SafeCommand::new("timeout")
.unwrap()
.arg("5s")
.unwrap()
.arg("cargo")
.unwrap()
.arg("build")
.unwrap()
.validate();
assert!(cmd.is_ok());
assert_eq!(cmd.unwrap().to_string_debug(), "timeout 5s cargo build");
}
#[test]
fn test_rustfmt_with_path() {
let path = SafePath::new("src/generated/output.rs").unwrap();
let cmd = SafeCommand::new("rustfmt")
.unwrap()
.arg_path(&path)
.validate();
assert!(cmd.is_ok());
let cmd_str = cmd.unwrap().to_string_debug();
assert!(cmd_str.contains("rustfmt"));
assert!(cmd_str.contains("src/generated/output.rs"));
}
#[test]
fn test_command_with_no_args() {
let cmd = SafeCommand::new("git").unwrap().validate();
assert!(cmd.is_ok(), "Command with no args should be valid");
}
#[test]
fn test_command_with_many_short_args() {
let mut cmd = SafeCommand::new("cargo").unwrap();
for i in 0..1000 {
cmd = cmd.arg(format!("a{}", i)).unwrap();
}
let result = cmd.validate();
assert!(result.is_err(), "Many args should exceed max length");
}
#[test]
fn test_arg_with_equals_sign() {
let result = SafeCommand::new("cargo")
.unwrap()
.arg("--config=release")
.unwrap()
.validate();
assert!(result.is_ok(), "Equals sign should be allowed in args");
}
#[test]
fn test_arg_with_colon() {
let result = SafeCommand::new("cargo")
.unwrap()
.arg("package:name")
.unwrap()
.validate();
assert!(result.is_ok(), "Colon should be allowed in args");
}
#[test]
fn test_arg_with_slash() {
let result = SafeCommand::new("cargo")
.unwrap()
.arg("path/to/file")
.unwrap()
.validate();
assert!(result.is_ok(), "Slash should be allowed in args");
}
#[test]
fn test_arg_with_dot() {
let result = SafeCommand::new("cargo")
.unwrap()
.arg("file.rs")
.unwrap()
.validate();
assert!(result.is_ok(), "Dot should be allowed in args");
}
#[test]
fn test_arg_with_underscore_and_dash() {
let result = SafeCommand::new("cargo")
.unwrap()
.arg("my-package_name")
.unwrap()
.validate();
assert!(
result.is_ok(),
"Underscore and dash should be allowed in args"
);
}