33"""
44
55from pathlib import Path
6+ import re
67from fastapi import APIRouter , HTTPException
78from fastapi .responses import FileResponse
89from urllib .parse import unquote
910
1011router = APIRouter (prefix = "/debug/reports" , tags = ["debug-reports" ])
1112REPORTS_BASE_DIR = Path ("reports/accessibility" ).resolve ()
13+ SAFE_REPORT_FILENAME_RE = re .compile (r"^[A-Za-z0-9._-]+\.html$" )
1214
1315
1416@router .get ("/accessibility" )
@@ -17,18 +19,31 @@ def serve_accessibility_report(file_path: str):
1719 try :
1820 # Decode URL-encoded path
1921 print ("Serving accessi file" )
20- decoded_path = unquote (file_path )
21- requested_path = Path (decoded_path )
22+ decoded_path = unquote (file_path ).strip ()
2223
23- # Reject absolute paths and traversal attempts before joining
24- if requested_path .is_absolute () or ".." in requested_path .parts :
24+ # Accept only a safe filename (no directory components)
25+ if not decoded_path :
26+ raise HTTPException (
27+ status_code = 400 ,
28+ detail = "Invalid report path"
29+ )
30+
31+ # Reject any path separators or dot-directory tokens
32+ if "/" in decoded_path or "\\ " in decoded_path or decoded_path in {"." , ".." }:
33+ raise HTTPException (
34+ status_code = 400 ,
35+ detail = "Invalid report path"
36+ )
37+
38+ # Ensure it is a basename and matches allowed characters + .html extension
39+ if Path (decoded_path ).name != decoded_path or not SAFE_REPORT_FILENAME_RE .match (decoded_path ):
2540 raise HTTPException (
2641 status_code = 400 ,
2742 detail = "Invalid report path"
2843 )
2944
3045 # Resolve path under a fixed reports directory to prevent traversal
31- html_file = (REPORTS_BASE_DIR / requested_path ).resolve ()
46+ html_file = (REPORTS_BASE_DIR / decoded_path ).resolve ()
3247 try :
3348 html_file .relative_to (REPORTS_BASE_DIR )
3449 except ValueError :
0 commit comments