Merge commit from fork

Fix static file path traversal
This commit is contained in:
Stalin
2026-05-21 18:20:21 +05:30
committed by GitHub
+17 -7
View File
@@ -5,12 +5,13 @@ A lightweight package store API for Arch Linux.
import sys import sys
import os import os
from pathlib import Path
# Add backend directory to path for imports # Add backend directory to path for imports
sys.path.insert(0, os.path.dirname(__file__)) sys.path.insert(0, os.path.dirname(__file__))
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from fastapi import FastAPI from fastapi import FastAPI, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
@@ -84,16 +85,25 @@ if os.path.exists(STATIC_DIR):
if os.path.exists(assets_dir): if os.path.exists(assets_dir):
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets") app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
static_root = Path(STATIC_DIR).resolve()
def resolve_static_path(requested_path: str) -> Path:
candidate = (static_root / requested_path).resolve()
if static_root == candidate or static_root in candidate.parents:
return candidate
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not found")
@app.get("/{fallback_path:path}") @app.get("/{fallback_path:path}")
async def serve_frontend(fallback_path: str): async def serve_frontend(fallback_path: str):
# Serve favicon or other root files directly if they exist # Serve favicon or other root files directly if they exist
file_path = os.path.join(STATIC_DIR, fallback_path) if fallback_path:
if fallback_path and os.path.exists(file_path) and os.path.isfile(file_path): safe_path = resolve_static_path(fallback_path)
return FileResponse(file_path) if safe_path.exists() and safe_path.is_file():
return FileResponse(str(safe_path))
# Otherwise fallback to index.html for React SPA routing # Otherwise fallback to index.html for React SPA routing
index_path = os.path.join(STATIC_DIR, "index.html") index_path = static_root / "index.html"
if os.path.exists(index_path): if index_path.exists():
return FileResponse(index_path) return FileResponse(str(index_path))
else: else:
@app.get("/") @app.get("/")
async def root(): async def root():