Broken Object Level Authorization

How to Fix Broken Object Level Authorization in FastAPI [March 2026] [CVE-2026-2976]

[Updated March 2026] Updated CVE-2026-2976

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))

CVE References

Choose which optional cookies to allow. You can change this any time.