harry0703/MoneyPrinterTurbo · error · HttpException

{request_id}: invalid file path

Error message

{request_id}: invalid file path

What it means

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.

Source

Thrown at app/controllers/v1/video.py:86

    normalized_name = (filename or "").replace("\\", "/").split("/")[-1].strip()
    if not normalized_name or normalized_name in {".", ".."}:
        raise HttpException(
            task_id=request_id,
            status_code=400,
            message=f"{request_id}: invalid filename",
        )
    return normalized_name


def _resolve_path_within_directory(base_dir: str, unsafe_path: str, request_id: str) -> str:
    try:
        return file_security.resolve_path_within_directory(base_dir, unsafe_path)
    except ValueError as exc:
        logger.warning(
            f"reject unsafe file path, request_id: {request_id}, path: {unsafe_path}, "
            f"error: {str(exc)}"
        )
        raise HttpException(
            task_id=request_id,
            status_code=404 if str(exc) == "file does not exist" else 403,
            message=f"{request_id}: invalid file path",
        )


def _public_task_data(task: dict) -> dict:
    """复制任务状态并移除仅用于服务端进程协调的内部字段。"""
    public_task = dict(task)
    public_task.pop("cross_post_owner", None)
    return public_task


def _task_file_to_uri(file: str, endpoint: str, task_dir: str, request_id: str) -> str:
    if not isinstance(file, str):
        return file

    if file.startswith(("http://", "https://")):

View on GitHub (pinned to 1f9f19c202)

Solutions

  1. Only use stream URIs exactly as returned by the API (the tasks/{task_id}/... paths from task responses); never construct them by hand.
  2. If you get 404, re-fetch the task to confirm it and its artifacts still exist; regenerate if the task was deleted.
  3. If you get 403, remove any ../ or leading-slash components from the path you are passing.
  4. Handle 404 by refreshing task state instead of retrying the same stale path.

Example fix

# before
video_uri = f"tasks/{task_id}/../../etc/passwd"

# after
resp = requests.get(f"{base}/api/v1/tasks/{task_id}", headers=h).json()
video_uri = resp[\"data\"][\"videos\"][0]  # server-issued, already-safe relative path
Defensive patterns

Strategy: validation

Validate before calling

# client: only consume server-issued relative URIs
task = requests.get(f"{base}/api/v1/tasks/{task_id}", headers=h).json()["data"]
uri = task["videos"][0]
assert not uri.startswith("/") and ".." not in uri  # sanity-check server-issued path

Type guard

def is_relative_task_path(p: str) -> bool:
    return bool(p) and not p.startswith(("/", "\\")) and ".." not in p.replace("\\", "/").split("/")

Try / catch

try:
    stream(url)
except HTTPError as e:
    if e.response.status_code == 404:
        refresh_task_state(task_id)  # artifact/task gone
    elif e.response.status_code == 403:
        raise RuntimeError("stream path rejected; use server-issued URIs only") from e
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of harry0703/MoneyPrinterTurbo@1f9f19c202 (2026-08-14). Data as JSON: /api/errors/ccacd8202ecf726f. Report an issue: GitHub.