Describe the bug
static_route_exts() and static_route() build file paths by string concatenation without normalizing or constraining fname to stay under static_path:
# fasthtml/core.py
FileResponse(f'{static_path}/{fname}.{ext}') # static_route_exts
FileResponse(f'{static_path}/{fname}{ext}') # static_route
Because {fname:path} can contain .. segments, a request may escape the intended static directory if the target file exists on disk.
Minimal Reproducible Example
import os, tempfile
from starlette.testclient import TestClient
from fasthtml.common import fast_app
tmpdir = tempfile.mkdtemp()
parent = os.path.dirname(tmpdir)
open(os.path.join(parent, "secret.txt"), "w").write("LEAKED")
app, rt = fast_app(static_path=tmpdir)
client = TestClient(app)
resp = client.get("/../secret.txt")
print(resp.status_code, resp.text)
Expected behavior
Static handlers should only serve files whose resolved path is inside static_path. Paths with .. should return 404.
Actual behavior
FileResponse is built with a path outside static_path. _resp() only checks os.path.exists(resp.path) without verifying the path stays within the static root.
Suggested fix
Resolve with Path(static_path, fname).resolve() and verify resolved.is_relative_to(Path(static_path).resolve()).
Environment Information
- fasthtml version: 0.14.x (main)
- Python: 3.11+
Confirmation
Describe the bug
static_route_exts()andstatic_route()build file paths by string concatenation without normalizing or constrainingfnameto stay understatic_path:Because
{fname:path}can contain..segments, a request may escape the intended static directory if the target file exists on disk.Minimal Reproducible Example
Expected behavior
Static handlers should only serve files whose resolved path is inside
static_path. Paths with..should return 404.Actual behavior
FileResponseis built with a path outsidestatic_path._resp()only checksos.path.exists(resp.path)without verifying the path stays within the static root.Suggested fix
Resolve with
Path(static_path, fname).resolve()and verifyresolved.is_relative_to(Path(static_path).resolve()).Environment Information
Confirmation