fix(security): enhance path traversal protection in file uploads

- Import and use sanitize_filename utility in ui_upload endpoint
- Enhance sanitize_filename to handle Windows-style paths (backslashes)
- Add protection against path traversal patterns (..)
- Replace all path separators with underscores
- Add comprehensive security tests for Windows-style paths and mixed separators
- All existing tests pass with improved security

This addresses the "Uncontrolled data used in path expression" code scanning alert
by ensuring all user-provided filenames are properly sanitized before being used
in any file operations or stored in the database.

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-09 21:00:51 +00:00
parent ef484f85a4
commit 2bcd774d6d
3 changed files with 69 additions and 7 deletions
+46 -2
View File
@@ -272,10 +272,54 @@ class TestUploadSecurity:
assert response.status_code == 200
data = response.json()
# Original filename should be preserved (sanitized by basename)
assert data["original_filename"] == special_filename
# Original filename should be sanitized (special chars replaced with underscores)
assert "!" not in data["original_filename"]
assert "@" not in data["original_filename"]
assert "#" not in data["original_filename"]
# Spaces and basic characters should be preserved
assert "file" in data["original_filename"]
assert ".pdf" in data["original_filename"]
# Stored filename should have UUID and extension
assert data["stored_filename"].endswith(".pdf")
def test_path_traversal_prevention_windows_style(self, client: TestClient, mock_celery_tasks):
"""Test that Windows-style path traversal attempts are prevented."""
# Try Windows-style path with backslashes
malicious_filename = "..\\..\\..\\windows\\system32\\config.pdf"
pdf_content = b"%PDF-1.4\n%EOF"
response = client.post(
"/api/ui-upload",
files={"file": (malicious_filename, io.BytesIO(pdf_content), "application/pdf")}
)
assert response.status_code == 200
data = response.json()
# Backslashes should be removed or replaced
assert "\\" not in data["original_filename"]
assert ".." not in data["original_filename"]
# Should contain sanitized version
assert "config.pdf" in data["original_filename"]
def test_path_traversal_prevention_mixed_separators(self, client: TestClient, mock_celery_tasks):
"""Test handling of filenames with mixed path separators."""
malicious_filename = "../path\\to/file.pdf"
pdf_content = b"%PDF-1.4\n%EOF"
response = client.post(
"/api/ui-upload",
files={"file": (malicious_filename, io.BytesIO(pdf_content), "application/pdf")}
)
assert response.status_code == 200
data = response.json()
# Should only keep the filename without any path components
assert "/" not in data["original_filename"]
assert "\\" not in data["original_filename"]
assert ".." not in data["original_filename"]
assert "file.pdf" in data["original_filename"]
@pytest.mark.integration