Files
Fast-Whisper-MCP-Server/tests/test_path_traversal_fix.py
Alihan 990fa28668 Fix path traversal false positives for filenames with ellipsis
Replace naive string-based ".." detection with component-based analysis
to eliminate false positives while maintaining security.

Problem:
- Filenames like "Battery... Rekon 35.m4a" were incorrectly flagged
- String check `if ".." in path` matched ellipsis (...) as traversal

Solution:
- Parse path into components using Path().parts
- Check each component for exact ".." match
- Allows ellipsis in filenames while blocking actual traversal

Security maintained:
-  Blocks: ../etc/passwd, dir/../../secret, /../../../etc/hosts
-  Allows: file...mp3, Wait... what.m4a, Battery...Rekon.m4a

Tests:
- Added comprehensive test suite with 8 test cases
- Verified ellipsis filenames pass validation
- Verified path traversal attacks still blocked
- All tests passing (8/8)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 23:14:39 +03:00

209 lines
7.3 KiB
Python

#!/usr/bin/env python3
"""
Test path traversal detection with ellipsis support.
Tests the fix for false positives where filenames containing ellipsis (...)
were incorrectly flagged as path traversal attempts.
"""
import pytest
import sys
import os
from pathlib import Path
import tempfile
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from utils.input_validation import (
validate_path_safe,
validate_audio_file,
PathTraversalError,
ValidationError,
InvalidFileTypeError
)
class TestPathTraversalWithEllipsis:
"""Test that ellipsis in filenames is allowed while blocking real attacks."""
def test_filename_with_ellipsis_allowed(self, tmp_path):
"""Filenames with ellipsis (...) should be allowed."""
test_cases = [
"Wait... what.mp3",
"This Weird FPV Drone only takes one kind of Battery... Rekon 35 V2.m4a",
"file...mp3",
"test....audio.wav",
"a..b..c.mp3",
"dots.........everywhere.m4a"
]
for filename in test_cases:
# Create test file
test_file = tmp_path / filename
test_file.write_text("fake audio data")
# Should NOT raise PathTraversalError
try:
result = validate_path_safe(str(test_file), [str(tmp_path)])
assert result.exists(), f"File should exist: {filename}"
print(f"✓ PASS: {filename}")
except PathTraversalError as e:
pytest.fail(f"False positive for filename: {filename}. Error: {e}")
def test_actual_path_traversal_blocked(self, tmp_path):
"""Actual path traversal attempts should be blocked."""
attack_cases = [
"../../../etc/passwd",
"..\\..\\..\\windows\\system32",
"legitimate/../../../etc/passwd",
"dir/../../secret",
"../",
"..",
"subdir/../../../etc/hosts"
]
for attack_path in attack_cases:
with pytest.raises(PathTraversalError):
validate_path_safe(attack_path, [str(tmp_path)])
print(f"✗ FAIL: Should have blocked: {attack_path}")
print(f"✓ PASS: Blocked attack: {attack_path}")
def test_ellipsis_in_full_path_allowed(self, tmp_path):
"""Full paths with ellipsis in filename should be allowed."""
# Create nested directory
subdir = tmp_path / "uploads"
subdir.mkdir()
filename = "Wait... what.mp3"
test_file = subdir / filename
test_file.write_text("fake audio data")
# Full path should be allowed when directory is in allowed_dirs
result = validate_path_safe(str(test_file), [str(tmp_path)])
assert result.exists()
print(f"✓ PASS: Full path with ellipsis: {test_file}")
def test_mixed_dots_edge_cases(self, tmp_path):
"""Test edge cases with various dot patterns."""
edge_cases = [
("single.dot.mp3", True), # Normal filename
("..two.dots.mp3", True), # Starts with two dots (filename)
("three...dots.mp3", True), # Three consecutive dots
("many.....dots.mp3", True), # Many consecutive dots
(".", False), # Current directory (should fail)
("..", False), # Parent directory (should fail)
]
for filename, should_pass in edge_cases:
if should_pass:
# Create test file
test_file = tmp_path / filename
test_file.write_text("fake audio data")
try:
result = validate_path_safe(str(test_file), [str(tmp_path)])
assert result.exists(), f"File should exist: {filename}"
print(f"✓ PASS: Allowed: {filename}")
except PathTraversalError:
pytest.fail(f"Should have allowed: {filename}")
else:
with pytest.raises((PathTraversalError, ValidationError)):
validate_path_safe(filename, [str(tmp_path)])
print(f"✓ PASS: Blocked: {filename}")
class TestAudioFileValidationWithEllipsis:
"""Test full audio file validation with ellipsis support."""
def test_audio_file_with_ellipsis(self, tmp_path):
"""Audio files with ellipsis should pass validation."""
filename = "This Weird FPV Drone only takes one kind of Battery... Rekon 35 V2.m4a"
test_file = tmp_path / filename
test_file.write_bytes(b"fake audio data" * 100) # Non-empty file
# Should pass validation
result = validate_audio_file(str(test_file), [str(tmp_path)])
assert result.exists()
print(f"✓ PASS: Audio validation with ellipsis: {filename}")
def test_audio_file_traversal_attack_blocked(self, tmp_path):
"""Audio file validation should block path traversal."""
attack_path = "../../../etc/passwd"
with pytest.raises(PathTraversalError):
validate_audio_file(attack_path, [str(tmp_path)])
print(f"✓ PASS: Audio validation blocked attack: {attack_path}")
class TestComponentBasedDetection:
"""Test that detection is based on path components, not string matching."""
def test_component_analysis(self, tmp_path):
"""Verify that we're analyzing components, not doing string matching."""
# These should PASS (ellipsis is in the filename component)
safe_cases = [
tmp_path / "file...mp3",
tmp_path / "subdir" / "Wait...what.m4a",
]
for test_path in safe_cases:
test_path.parent.mkdir(parents=True, exist_ok=True)
test_path.write_text("data")
# Check that ".." is not in any component
parts = Path(test_path).parts
assert not any(part == ".." for part in parts), \
f"Should not have '..' as a component: {test_path}"
# Validation should pass
result = validate_path_safe(str(test_path), [str(tmp_path)])
assert result.exists()
print(f"✓ PASS: Component analysis correct: {test_path}")
def test_component_attack_detection(self):
"""Verify that actual '..' components are detected."""
# These should FAIL ('..' is a path component)
attack_cases = [
"../etc/passwd",
"dir/../secret",
"../../file.mp3",
]
for attack_path in attack_cases:
path = Path(attack_path)
parts = path.parts
# Verify that ".." IS in components
assert any(part == ".." for part in parts), \
f"Should have '..' as a component: {attack_path}"
print(f"✓ PASS: Attack has '..' component: {attack_path}")
def run_tests():
"""Run all tests with verbose output."""
print("=" * 70)
print("Running Path Traversal Detection Tests")
print("=" * 70)
# Run pytest with verbose output
exit_code = pytest.main([
__file__,
"-v",
"--tb=short",
"-p", "no:warnings"
])
print("=" * 70)
if exit_code == 0:
print("✓ All tests passed!")
else:
print("✗ Some tests failed!")
print("=" * 70)
return exit_code
if __name__ == "__main__":
sys.exit(run_tests())