fix(api): block path traversal in download_work_dir_file (CVE-2026-4307)

Reject download requests whose resolved path escapes the runtime base
directory before file metadata lookup or streaming.

This keeps valid in-base absolute paths working in both Docker and
development setups while preventing arbitrary file reads via
/download_work_dir_file (CVE-2026-4307).

Reported by Edward-x (@YLChen-007). Thanks again.
Refs:
- https://nvd.nist.gov/vuln/detail/CVE-2026-4307
- https://gist.github.com/YLChen-007/1819c843ad26aaaaecdc768a789df022
- https://vuldb.com/vuln/351337/cti
This commit is contained in:
Alessandro 2026-04-12 02:31:24 +02:00
parent 071194281c
commit 0e3e8a159a

View file

@ -2,6 +2,7 @@ import base64
from io import BytesIO
import mimetypes
import os
from pathlib import Path
from flask import Response
from helpers.api import ApiHandler, Input, Output, Request
@ -85,6 +86,24 @@ def make_disposition(download_name: str) -> str:
return f'attachment; filename="{ascii_fallback}"; filename*=UTF-8\'\'{utf8_name}'
def resolve_download_path(path: str) -> str:
"""Resolve a requested download path and keep it within the runtime base dir."""
base_dir = Path(files.get_base_dir()).resolve()
candidate = Path(path)
if candidate.is_absolute():
resolved = candidate.resolve()
else:
resolved = (base_dir / candidate).resolve()
try:
resolved.relative_to(base_dir)
except ValueError as exc:
raise ValueError("Invalid file path") from exc
return str(resolved)
class DownloadFile(ApiHandler):
@classmethod
@ -98,6 +117,13 @@ class DownloadFile(ApiHandler):
if not file_path.startswith("/"):
file_path = f"/{file_path}"
try:
file_path = await runtime.call_development_function(
resolve_download_path, file_path
)
except ValueError as exc:
return Response(str(exc), status=400)
file = await runtime.call_development_function(
file_info.get_file_info, file_path
)