Overview
CVE-2026-2976 describes a vulnerability in FastApiAdmin up to 2.2.0 where the download endpoint accepts a file_path argument and uses it to locate and serve files without proper authorization. This leads to information disclosure (CWE-200) because an attacker can request arbitrary files by supplying a crafted file_path, potentially including absolute paths. The vulnerability represents broken object level authorization: the endpoint implicitly trusts user input to identify the object (a file) without verifying that the requester has rights to access it, and the attack can be executed remotely since the endpoint is exposed publicly. The CVE maps to CWE-434 (Unrestricted Upload/Download of files) and CWE-284/CWE-200 patterns where insufficient access controls enable data leakage via file paths or directory traversal. The public exploit further demonstrates how lack of per-object access checks on a download endpoint can enable attackers to exfiltrate sensitive data. In real-world FastAPI deployments, this class of issue frequently arises when a path or object identifier is used directly to fetch resources without authorization checks.
Affected Versions
FastApiAdmin up to 2.2.0 (inclusive); CVE-2026-2976
Code Fix Example
FastAPI API Security Remediation
Vulnerable:
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from pathlib import Path
app = FastAPI()
BASE_DIR = Path("/var/app/downloads")
@app.get("/download")
def download(file_path: str):
# Vulnerable: user-controlled path with no authorization or path validation
path = BASE_DIR / file_path # If file_path is absolute, Path / will ignore BASE_DIR
if not path.exists() or not path.is_file():
raise HTTPException(status_code=404, detail="Not found")
return FileResponse(str(path))
# Fixed:
from fastapi.security import OAuth2PasswordBearer
from fastapi import Depends
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
def get_current_user(token: str = Depends(oauth2_scheme)):
# In production, replace this with proper token validation and user lookup
if not token or token != "valid-token-example":
raise HTTPException(status_code=401, detail="Invalid authentication credentials")
return {"username": "secure_user"}
ALLOWED_FILES = {"report.pdf", "data.csv"}
BASE_DIR_SAFE = BASE_DIR.resolve()
@app.get("/download-secure")
def download_secure(file_path: str, current_user: dict = Depends(get_current_user)):
# Fixed: resolve path, constrain to base directory, and enforce an allowlist
requested = (BASE_DIR / file_path).resolve()
if not str(requested).startswith(str(BASE_DIR_SAFE)):
raise HTTPException(status_code=403, detail="Forbidden")
if requested.name not in ALLOWED_FILES:
raise HTTPException(status_code=403, detail="Forbidden")
return FileResponse(str(requested))