{"record":{"id":"ccacd8202ecf726f","repo":"harry0703/MoneyPrinterTurbo","slug":"request-id-invalid-file-path","errorCode":null,"errorMessage":"{request_id}: invalid file path","messagePattern":"(.+?): invalid file path","errorType":"http","errorClass":"HttpException","httpStatus":403,"severity":"error","filePath":"app/controllers/v1/video.py","lineNumber":86,"sourceCode":"    normalized_name = (filename or \"\").replace(\"\\\\\", \"/\").split(\"/\")[-1].strip()\n    if not normalized_name or normalized_name in {\".\", \"..\"}:\n        raise HttpException(\n            task_id=request_id,\n            status_code=400,\n            message=f\"{request_id}: invalid filename\",\n        )\n    return normalized_name\n\n\ndef _resolve_path_within_directory(base_dir: str, unsafe_path: str, request_id: str) -> str:\n    try:\n        return file_security.resolve_path_within_directory(base_dir, unsafe_path)\n    except ValueError as exc:\n        logger.warning(\n            f\"reject unsafe file path, request_id: {request_id}, path: {unsafe_path}, \"\n            f\"error: {str(exc)}\"\n        )\n        raise HttpException(\n            task_id=request_id,\n            status_code=404 if str(exc) == \"file does not exist\" else 403,\n            message=f\"{request_id}: invalid file path\",\n        )\n\n\ndef _public_task_data(task: dict) -> dict:\n    \"\"\"复制任务状态并移除仅用于服务端进程协调的内部字段。\"\"\"\n    public_task = dict(task)\n    public_task.pop(\"cross_post_owner\", None)\n    return public_task\n\n\ndef _task_file_to_uri(file: str, endpoint: str, task_dir: str, request_id: str) -> str:\n    if not isinstance(file, str):\n        return file\n\n    if file.startswith((\"http://\", \"https://\")):","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/controllers/v1/video.py#L68-L104","documentation":"Raised by _resolve_path_within_directory in app/controllers/v1/video.py when file_security.resolve_path_within_directory rejects the caller-supplied path relative to the task directory. The status is 404 when the underlying error is 'file does not exist' and 403 for any other failure (typically path traversal outside base_dir). The response text is deliberately stable ('{request_id}: invalid file path') so server paths don't leak.","triggerScenarios":"GET /api/v1/stream/<path> where path contains ../ sequences escaping the tasks directory (403); streaming a file whose task directory or artifact no longer exists (404); URL-encoded traversal like %2e%2e%2f; referencing an artifact belonging to a deleted task.","commonSituations":"Stale client-held URIs after a task was deleted or its directory cleaned; hand-crafted or fuzzed URLs probing for traversal; double-encoding bugs in a frontend router that mangles the relative path.","solutions":["Only use stream URIs exactly as returned by the API (the tasks/{task_id}/... paths from task responses); never construct them by hand.","If you get 404, re-fetch the task to confirm it and its artifacts still exist; regenerate if the task was deleted.","If you get 403, remove any ../ or leading-slash components from the path you are passing.","Handle 404 by refreshing task state instead of retrying the same stale path."],"exampleFix":"# before\nvideo_uri = f\"tasks/{task_id}/../../etc/passwd\"\n\n# after\nresp = requests.get(f\"{base}/api/v1/tasks/{task_id}\", headers=h).json()\nvideo_uri = resp[\\\"data\\\"][\\\"videos\\\"][0]  # server-issued, already-safe relative path","handlingStrategy":"validation","validationCode":"# client: only consume server-issued relative URIs\ntask = requests.get(f\"{base}/api/v1/tasks/{task_id}\", headers=h).json()[\"data\"]\nuri = task[\"videos\"][0]\nassert not uri.startswith(\"/\") and \"..\" not in uri  # sanity-check server-issued path","typeGuard":"def is_relative_task_path(p: str) -> bool:\n    return bool(p) and not p.startswith((\"/\", \"\\\\\")) and \"..\" not in p.replace(\"\\\\\", \"/\").split(\"/\")","tryCatchPattern":"try:\n    stream(url)\nexcept HTTPError as e:\n    if e.response.status_code == 404:\n        refresh_task_state(task_id)  # artifact/task gone\n    elif e.response.status_code == 403:\n        raise RuntimeError(\"stream path rejected; use server-issued URIs only\") from e\n    raise","preventionTips":["Never build stream paths yourself; always use the URI from the task response.","Refresh task state after receiving 404 instead of retrying the same path.","URL-encode the path exactly once; double-encoding can turn into traversal-looking input."],"tags":["path-traversal","streaming","http-403","http-404"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}