Overview
Broken Object Level Authorization (BOLA) in FastAPI enables a user to access or modify resources they should not own due to missing ownership checks. Real world impact ranges from viewing another user's data to tampering with resources across accounts, potentially leading to data leakage, fraud, and regulatory risk. This guide describes the class of vulnerabilities, why they occur in FastAPI apps, and how attackers can exploit predictable object identifiers without proper checks. Note: no CVE IDs are provided here, but the pattern reflects widely observed security issues in web services.
In FastAPI, BOLA often shows up when an endpoint fetches an object by id from the path and returns it to the caller without confirming ownership. If endpoints also allow updates or deletions without proper ownership validation, an attacker can perform actions on other users' resources by enumerating IDs.
Remediation patterns for FastAPI: 1) Introduce a robust authorization check at the boundary using a dependency that loads the current user and ties every resource to an owner_id. 2) In each critical path, verify item.owner_id == current_user.id before returning or mutating. 3) Integrate with the ORM layer to filter by owner_id, or implement row-level security per query. 4) Avoid leaking ownership metadata and resource IDs where not necessary, and 5) Invest in automated tests that exercise cross-user access attempts.
Testing and validation: create unit/integration tests that attempt to access another user's items; add property-based tests; run security scanners to detect insufficient authorization checks; continuously monitor and update dependencies; consider using policy frameworks or RBAC to centralize access control.
Code Fix Example
FastAPI API Security Remediation
# Vulnerable pattern: endpoint returns an object without verifying ownership
from fastapi import FastAPI, Depends, HTTPException
from typing import Dict
app = FastAPI()
DB: Dict[int, Dict] = {
1: {'id': 1, 'owner_id': 42, 'data': 'secretA'},
2: {'id': 2, 'owner_id': 99, 'data': 'secretB'},
}
class User:
def __init__(self, id: int):
self.id = id
def get_current_user():
# In real usage, extract user from auth token
return User(42)
@app.get('/items/{item_id}')
def read_item(item_id: int, user=Depends(get_current_user)):
item = DB.get(item_id)
if not item:
raise HTTPException(status_code=404, detail='Not found')
# Vulnerable: no authorization check
return item
# Secure pattern: enforce object-level authorization
@app.get('/secure/items/{item_id}')
def read_item_secure(item_id: int, user=Depends(get_current_user)):
item = DB.get(item_id)
if not item:
raise HTTPException(status_code=404, detail='Not found')
if item['owner_id'] != user.id:
raise HTTPException(status_code=403, detail='Not authorized')
return item