fix(web): sanitize username in report file paths to prevent path traversal (#2678)

* fix: sanitize username in report file paths to prevent path traversal (CWE-22)

* address review: add unit tests for sanitize_username_for_path()
This commit is contained in:
Sebastion
2026-05-20 23:55:17 +02:00
committed by GitHub
parent 992da86284
commit e6f53862f5
2 changed files with 44 additions and 5 deletions
+16 -5
View File
@@ -99,6 +99,16 @@ async def search_multiple_usernames(usernames, options):
return results
def sanitize_username_for_path(username: str) -> str:
"""Remove path separators and dangerous components from username for safe file path usage."""
# Replace path separators and null bytes
sanitized = username.replace('/', '_').replace('\\', '_').replace('\0', '_')
# Remove . and .. components
sanitized = sanitized.strip('.')
# If empty after sanitization, use a fallback
return sanitized or '_'
def process_search_task(usernames, options, timestamp):
try:
loop = asyncio.new_event_loop()
@@ -123,7 +133,8 @@ def process_search_task(usernames, options, timestamp):
individual_reports = []
for username, id_type, results in general_results:
report_base = os.path.join(session_folder, f"report_{username}")
safe_username = sanitize_username_for_path(username)
report_base = os.path.join(session_folder, f"report_{safe_username}")
csv_path = f"{report_base}.csv"
json_path = f"{report_base}.json"
@@ -162,16 +173,16 @@ def process_search_task(usernames, options, timestamp):
{
'username': username,
'csv_file': os.path.join(
f"search_{timestamp}", f"report_{username}.csv"
f"search_{timestamp}", f"report_{safe_username}.csv"
),
'json_file': os.path.join(
f"search_{timestamp}", f"report_{username}.json"
f"search_{timestamp}", f"report_{safe_username}.json"
),
'pdf_file': os.path.join(
f"search_{timestamp}", f"report_{username}.pdf"
f"search_{timestamp}", f"report_{safe_username}.pdf"
),
'html_file': os.path.join(
f"search_{timestamp}", f"report_{username}.html"
f"search_{timestamp}", f"report_{safe_username}.html"
),
'claimed_profiles': claimed_profiles,
}
+28
View File
@@ -0,0 +1,28 @@
import pytest
from maigret.web.app import sanitize_username_for_path
@pytest.mark.parametrize(
"input_username, expected",
[
("../../tmp/x", "_.._tmp_x"),
("..", "_"),
("....", "_"),
("foo/bar", "foo_bar"),
("\0foo", "_foo"),
("normaluser123", "normaluser123"),
],
)
def test_sanitize_username_for_path(input_username, expected):
result = sanitize_username_for_path(input_username)
assert result == expected
# Verify no path separators or null bytes remain
assert "/" not in result
assert "\\" not in result
assert "\0" not in result
# Verify result is not empty
assert len(result) > 0
# Verify no leading/trailing dots
assert not result.startswith(".")
assert not result.endswith(".")