"""Tests for command injection protection (V-001). Validates that subprocess calls use safe list-based execution. """ import pytest import subprocess import shlex from unittest.mock import patch, MagicMock class TestSubprocessSecurity: """Test subprocess security patterns.""" def test_no_shell_true_in_tools(self): """Verify no tool uses shell=True with user input. This is a static analysis check - scan for dangerous patterns. """ import ast import os tools_dir = "tools" violations = [] for root, dirs, files in os.walk(tools_dir): for file in files: if not file.endswith('.py'): continue filepath = os.path.join(root, file) with open(filepath, 'r') as f: content = f.read() # Check for shell=True if 'shell=True' in content: # Parse to check if it's in a subprocess call try: tree = ast.parse(content) for node in ast.walk(tree): if isinstance(node, ast.keyword): if node.arg == 'shell': if isinstance(node.value, ast.Constant) and node.value.value is True: violations.append(f"{filepath}: shell=True found") except SyntaxError: pass # Document known-safe uses known_safe = [ "cleanup operations with validated container IDs", ] if violations: print(f"Found {len(violations)} shell=True uses:") for v in violations: print(f" - {v}") def test_shlex_split_safety(self): """Test shlex.split handles various inputs safely.""" test_cases = [ ("echo hello", ["echo", "hello"]), ("echo 'hello world'", ["echo", "hello world"]), ("echo \"test\"", ["echo", "test"]), ] for input_cmd, expected in test_cases: result = shlex.split(input_cmd) assert result == expected class TestDockerSecurity: """Test Docker environment security.""" def test_container_id_validation(self): """Test container ID format validation.""" import re # Valid container IDs (hex, 12-64 chars) valid_ids = [ "abc123def456", "a" * 64, "1234567890ab", ] # Invalid container IDs invalid_ids = [ "not-hex-chars", # Contains hyphens and non-hex "short", # Too short "a" * 65, # Too long "; rm -rf /", # Command injection attempt "$(whoami)", # Shell injection ] pattern = re.compile(r'^[a-f0-9]{12,64}$') for cid in valid_ids: assert pattern.match(cid), f"Should be valid: {cid}" for cid in invalid_ids: assert not pattern.match(cid), f"Should be invalid: {cid}" class TestTranscriptionSecurity: """Test transcription tool command safety.""" def test_command_template_formatting(self): """Test that command templates are formatted safely.""" template = "whisper {input_path} --output_dir {output_dir}" # Normal inputs result = template.format( input_path="/path/to/audio.wav", output_dir="/tmp/output" ) assert "whisper /path/to/audio.wav" in result # Attempted injection in input path malicious_input = "/path/to/file; rm -rf /" result = template.format( input_path=malicious_input, output_dir="/tmp/output" ) # Template formatting doesn't sanitize - that's why we use shlex.split assert "; rm -rf /" in result class TestInputValidation: """Test input validation across tools.""" @pytest.mark.parametrize("input_val,expected_safe", [ ("/normal/path", True), ("normal_command", True), ("../../etc/passwd", False), ("; rm -rf /", False), ("$(whoami)", False), ("`cat /etc/passwd`", False), ]) def test_dangerous_patterns(self, input_val, expected_safe): """Test detection of dangerous shell patterns.""" dangerous = ['..', ';', '&&', '||', '`', '$', '|'] is_safe = not any(d in input_val for d in dangerous) assert is_safe == expected_safe