{"record":{"id":"0f5cbf3650074bd6","repo":"abi/screenshot-to-code","slug":"invalid-report-filename","errorCode":null,"errorMessage":"Invalid report filename","messagePattern":"Invalid report filename","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"backend/routes/prompt_reports.py","lineNumber":154,"sourceCode":"    reports.sort(key=lambda report: (report.created_at, report.turn), reverse=True)\n\n    total_size_bytes = (\n        _directory_size_bytes(run_logs_directory)\n        if os.path.isdir(run_logs_directory)\n        else 0\n    )\n\n    return PromptReportListResponse(\n        reports=reports,\n        total_size_bytes=total_size_bytes,\n        reports_directory=reports_directory,\n    )\n\n\n@router.get(\"/prompt-reports/content\")\nasync def get_prompt_report_content(filename: str) -> Any:\n    if PROMPT_REPORT_FILENAME_PATTERN.match(filename) is None:\n        raise HTTPException(status_code=400, detail=\"Invalid report filename\")\n\n    filepath = os.path.join(get_prompt_reports_directory(), filename)\n    if not os.path.isfile(filepath):\n        raise HTTPException(status_code=404, detail=\"Report not found\")\n\n    try:\n        with open(filepath, \"r\", encoding=\"utf-8\") as f:\n            return json.load(f)\n    except (OSError, json.JSONDecodeError) as e:\n        raise HTTPException(status_code=500, detail=f\"Failed to read report: {e}\")\n\n\n@router.post(\"/prompt-reports/prune\", response_model=PrunePromptReportsResponse)\nasync def prune_prompt_reports(\n    request: PrunePromptReportsRequest,\n) -> PrunePromptReportsResponse:\n    if request.max_age_days < 1:\n        raise HTTPException(status_code=400, detail=\"max_age_days must be >= 1\")","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/abi/screenshot-to-code/blob/d026163f586dfa8c5c10d28c36edd59a9d3b0e88/backend/routes/prompt_reports.py#L136-L172","documentation":"400 raised by GET /prompt-reports/content in backend/routes/prompt_reports.py:154 when the filename query parameter does not match PROMPT_REPORT_FILENAME_PATTERN — the strict schema ^prompt_report_<YYYYMMDD>_<HHMMSS>_<8 hex session>_t<turn>_<provider>_<model>.json$ (backend/fs_logging/prompt_reports.py:29). This is also a path-traversal guard: any ../ or unexpected character fails the match before the file is opened.","triggerScenarios":"Calling the endpoint with a hand-built filename, a filename copied with whitespace/case changes, a traversal attempt like ../../backend/.env, or a report written by an older naming scheme.","commonSituations":"Clients constructing the URL from report metadata fields instead of using the exact filename from the reports list; reports migrated from another machine with different naming; security scanners probing with traversal strings.","solutions":["Use the exact filename returned by the GET /prompt-reports listing endpoint (its summaries come from the same pattern).","URL-encode the filename when building the query string.","If the file legitimately exists but is named differently, it was not written by this logger — check the naming pattern before renaming to match."],"exampleFix":"// before\nconst url = `/prompt-reports/content?filename=${reportDate}_${reportTime}.json`;\n\n// after\nconst url = `/prompt-reports/content?filename=${encodeURIComponent(report.filename)}`; // exact value from GET /prompt-reports","handlingStrategy":"validation","validationCode":"const FILENAME_RE = /^prompt_report_\\d{8}_\\d{6}_[0-9a-f]{8}_t\\d+_[a-z0-9]+_[A-Za-z0-9.-]+\\.json$/;\nif (!FILENAME_RE.test(filename)) throw new Error(`Invalid report filename: ${filename}`);","typeGuard":"function isPromptReportFilename(name: string): boolean {\n  return /^prompt_report_\\d{8}_\\d{6}_[0-9a-f]{8}_t\\d+_[a-z0-9]+_[A-Za-z0-9.-]+\\.json$/.test(name);\n}","tryCatchPattern":"const res = await fetch(`/prompt-reports/content?filename=${encodeURIComponent(filename)}`);\nif (res.status === 400) { /* do not retry: name is malformed; re-fetch from list */ }\nif (res.status === 404) { /* report was pruned; refresh the list */ }","preventionTips":["Only use filenames verbatim from the GET /prompt-reports listing.","URL-encode filenames; never build them from date/model parts client-side."],"tags":["http-400","validation","path-traversal","prompt-reports","fastapi"],"backgroundTag":null,"analyzedSha":"d026163f586dfa8c5c10d28c36edd59a9d3b0e88","analyzedAt":"2026-08-14T22:02:06.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}