diff --git a/crates/tui/src/command_safety.rs b/crates/tui/src/command_safety.rs index 5bdebafd..93a0a643 100644 --- a/crates/tui/src/command_safety.rs +++ b/crates/tui/src/command_safety.rs @@ -582,6 +582,14 @@ pub fn analyze_command(command: &str) -> SafetyAnalysis { ); } + if command.contains('\0') { + return SafetyAnalysis::dangerous( + command, + vec!["Command contains a null byte".to_string()], + vec!["Strip embedded null bytes before retrying".to_string()], + ); + } + if command.contains("&&") || command.contains("||") || command.contains(';') { // Chains of known-safe commands (cargo/git/zig/npm/etc.) are // routine for build+test workflows. Instead of hard-blocking, @@ -947,6 +955,39 @@ mod tests { ); } + #[test] + fn test_null_byte_is_blocked() { + assert_eq!( + analyze_command("ls\0 -la").level, + SafetyLevel::Dangerous, + "embedded NUL byte must be rejected as dangerous" + ); + assert_eq!( + analyze_command("echo hello\0world").level, + SafetyLevel::Dangerous + ); + } + + #[test] + fn test_eval_substring_is_not_misclassified() { + // Words like `evaluate` / `evaluation` / `cargo run -- eval` + // contain the substring "eval" but are not eval invocations. + // Guard against the naive `command.contains("eval")` regression + // — these should stay safe / workspace-safe, never Dangerous. + let evaluate_safe = analyze_command("cargo run --bin deepseek -- eval").level; + assert_ne!( + evaluate_safe, + SafetyLevel::Dangerous, + "running the eval harness should not be classified as dangerous" + ); + let evaluator = analyze_command("python evaluator.py --suite default").level; + assert_ne!( + evaluator, + SafetyLevel::Dangerous, + "running an evaluator script should not be classified as dangerous" + ); + } + #[test] fn test_privileged_commands() { assert_eq!(