Merge pull request #247 from unslothai/fix/path-traversal-vuln

Fix Content-Length crash and path traversal vulnerability in frontend serving
This commit is contained in:
Roland Tannous 2026-02-25 05:16:00 +04:00 committed by GitHub
commit b442bf1f84

View file

@ -8,7 +8,7 @@ from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from fastapi.responses import FileResponse, HTMLResponse, Response
from pathlib import Path
from datetime import datetime
@ -156,26 +156,44 @@ async def get_hardware_info():
def setup_frontend(app: FastAPI, build_path: Path):
"""Mount frontend static files (optional)"""
if build_path.exists():
# Mount assets
assets_dir = build_path / "assets"
if assets_dir.exists():
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
if not build_path.exists():
return False
@app.get("/")
async def serve_root():
return FileResponse(build_path / "index.html", headers={"Cache-Control": "no-cache, no-store, must-revalidate"})
# Mount assets
assets_dir = build_path / "assets"
if assets_dir.exists():
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
@app.get("/{full_path:path}")
async def serve_frontend(full_path: str):
if full_path.startswith("api"):
return {"error": "API endpoint not found"}
@app.get("/")
async def serve_root():
content = (build_path / "index.html").read_bytes()
return Response(
content=content,
media_type="text/html",
headers={"Cache-Control": "no-cache, no-store, must-revalidate"},
)
file_path = build_path / full_path
if file_path.is_file():
return FileResponse(file_path)
@app.get("/{full_path:path}")
async def serve_frontend(full_path: str):
if full_path.startswith("api"):
return {"error": "API endpoint not found"}
return FileResponse(build_path / "index.html", headers={"Cache-Control": "no-cache, no-store, must-revalidate"})
file_path = (build_path / full_path).resolve()
# Block path traversal — ensure resolved path stays inside build_path
if not str(file_path).startswith(str(build_path.resolve())):
return Response(status_code=403)
if file_path.is_file():
return FileResponse(file_path)
# Serve index.html as bytes — avoids Content-Length mismatch
content = (build_path / "index.html").read_bytes()
return Response(
content=content,
media_type="text/html",
headers={"Cache-Control": "no-cache, no-store, must-revalidate"},
)
return True
return True
return False