"""Comprehensive tests for path traversal protection (V-002). Validates that file operations correctly block malicious paths. """ import pytest import os import tempfile from pathlib import Path from unittest.mock import MagicMock, patch from tools.file_operations import ( _contains_path_traversal, _validate_safe_path, ShellFileOperations, ) class TestPathTraversalDetection: """Test path traversal pattern detection.""" @pytest.mark.parametrize("path,expected", [ # Unix-style traversal ("../../../etc/passwd", True), ("../secret.txt", True), ("foo/../../bar", True), # Windows-style traversal ("..\\..\\windows\\system32", True), ("foo\\..\\bar", True), # URL-encoded ("%2e%2e%2fetc%2fpasswd", True), ("%2E%2E/%2Ftest", True), # Double slash ("..//..//etc/passwd", True), # Tilde escape ("~/../../../etc/shadow", True), # Null byte injection ("/etc/passwd\x00.txt", True), # Safe paths ("/home/user/file.txt", False), ("./relative/path", False), ("~/documents/file", False), ("normal_file_name", False), ]) def test_contains_path_traversal(self, path, expected): """Test traversal pattern detection.""" result = _contains_path_traversal(path) assert result == expected, f"Path: {repr(path)}" class TestPathValidation: """Test comprehensive path validation.""" def test_validate_safe_path_valid(self): """Test valid paths pass validation.""" valid_paths = [ "/home/user/file.txt", "./relative/path", "~/documents", "normal_file", ] for path in valid_paths: is_safe, error = _validate_safe_path(path) assert is_safe is True, f"Path should be valid: {path} - {error}" def test_validate_safe_path_traversal(self): """Test traversal paths are rejected.""" is_safe, error = _validate_safe_path("../../../etc/passwd") assert is_safe is False assert "Path traversal" in error def test_validate_safe_path_null_byte(self): """Test null byte injection is blocked.""" is_safe, error = _validate_safe_path("/etc/passwd\x00.txt") assert is_safe is False def test_validate_safe_path_empty(self): """Test empty path is rejected.""" is_safe, error = _validate_safe_path("") assert is_safe is False assert "empty" in error.lower() def test_validate_safe_path_control_chars(self): """Test control characters are blocked.""" is_safe, error = _validate_safe_path("/path/with/\x01/control") assert is_safe is False assert "control" in error.lower() def test_validate_safe_path_very_long(self): """Test overly long paths are rejected.""" long_path = "a" * 5000 is_safe, error = _validate_safe_path(long_path) assert is_safe is False class TestShellFileOperationsSecurity: """Test security integration in ShellFileOperations.""" def test_read_file_blocks_traversal(self): """Test read_file rejects traversal paths.""" mock_env = MagicMock() ops = ShellFileOperations(mock_env) result = ops.read_file("../../../etc/passwd") assert result.error is not None assert "Security violation" in result.error def test_write_file_blocks_traversal(self): """Test write_file rejects traversal paths.""" mock_env = MagicMock() ops = ShellFileOperations(mock_env) result = ops.write_file("../../../etc/cron.d/backdoor", "malicious") assert result.error is not None assert "Security violation" in result.error class TestEdgeCases: """Test edge cases and bypass attempts.""" @pytest.mark.parametrize("path", [ # Mixed case "..%2F..%2Fetc%2Fpasswd", "%2e.%2f", # Unicode normalization bypasses "\u2025\u2025/etc/passwd", # Double dot characters "\u2024\u2024/etc/passwd", # One dot characters ]) def test_advanced_bypass_attempts(self, path): """Test advanced bypass attempts.""" # These should be caught by length or control char checks is_safe, _ = _validate_safe_path(path) # At minimum, shouldn't crash assert isinstance(is_safe, bool) class TestPerformance: """Test validation performance with many paths.""" def test_bulk_validation_performance(self): """Test that bulk validation is fast.""" import time paths = [ "/home/user/file" + str(i) + ".txt" for i in range(1000) ] start = time.time() for path in paths: _validate_safe_path(path) elapsed = time.time() - start # Should complete 1000 validations in under 1 second assert elapsed < 1.0, f"Validation too slow: {elapsed}s"