Pii Leakage in Fastapi
How PII Leakage Manifests in Fastapi
PII leakage in FastAPI applications often occurs through endpoint responses that unintentionally expose sensitive user data. FastAPI's automatic Pydantic model serialization can be a double-edged sword—while it simplifies development, it can also serialize entire database objects without proper field filtering.
Consider this common pattern:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
from models import User
app = FastAPI()
class UserResponse(BaseModel):
id: int
email: str
name: str
address: str
phone: str
ssn: str
credit_card: str
@app.get('/users/{user_id}', response_model=UserResponse)
async def get_user(user_id: int):
user = await User.objects.get(id=user_id)
return user
The vulnerability here is subtle: the endpoint returns a complete UserResponse model, but what if the database query returns a User object with additional fields like ssn or credit_card? FastAPI's Pydantic model will serialize only what's defined in UserResponse, but if the model definition includes sensitive fields, they'll be exposed.
Another FastAPI-specific pattern that leads to PII leakage is improper use of response_model_exclude and response_model_include:
@app.get('/users/{user_id}', response_model=User, response_model_exclude={'password'})
async def get_user(user_id: int):
user = await User.objects.get(id=user_id)
return user
This approach is fragile—if new sensitive fields are added to the User model, they'll automatically be exposed unless explicitly excluded. The response_model_exclude parameter can also be bypassed through query parameters if not properly validated.
FastAPI's dependency injection system can also create PII leakage paths. Consider this pattern:
from fastapi import Depends
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return credentials_exception
@app.get('/me')
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
If the User model contains sensitive fields and isn't properly filtered before being returned, the entire user object—including PII—is exposed to any authenticated user.
FastAPI's automatic OpenAPI documentation generation can also inadvertently expose PII through example responses. When generating OpenAPI specs, FastAPI uses Pydantic models to create example responses, which may include sensitive field examples in the documentation itself.
FastAPI-Specific Detection
Detecting PII leakage in FastAPI applications requires both static analysis and runtime scanning. Static analysis tools can examine your FastAPI route definitions and Pydantic models to identify potential PII exposure patterns.
middleBrick's FastAPI-specific scanning looks for several key indicators:
- Response models that include fields with PII-like names (ssn, credit_card, ssn, dob, etc.)
- Endpoints that return entire model instances without field filtering
- Improper use of response_model_exclude/include parameters
- Dependency injection patterns that pass full user objects to endpoints
- OpenAPI spec analysis showing sensitive fields in example responses
Here's how you'd scan a FastAPI application with middleBrick:
npm install -g middlebrick
middlebrick scan https://api.yourservice.com --fastapi
The --fastapi flag enables FastAPI-specific heuristics, including:
- Parsing FastAPI's OpenAPI spec to understand response model definitions
- Identifying Pydantic model schemas that contain PII fields
- Checking for FastAPI-specific patterns like dependency injection misuse
- Analyzing route decorators for potential exposure points
middleBrick's LLM security scanning is particularly relevant for FastAPI applications that integrate with AI services. FastAPI's async/await patterns make it a popular choice for building AI-powered APIs, and middleBrick can detect if your FastAPI endpoints are vulnerable to prompt injection attacks when calling LLM services.
For local development, you can use middleBrick's CLI to scan your FastAPI application before deployment:
middlebrick scan http://localhost:8000 --spec openapi.json
This approach allows you to catch PII leakage issues during development rather than in production.
FastAPI-Specific Remediation
FastAPI provides several native mechanisms to prevent PII leakage. The most effective approach is using dedicated response models that explicitly define what data should be exposed.
Instead of returning entire model instances, create specific response models:
from pydantic import BaseModel
from typing import Optional
class UserPublic(BaseModel):
id: int
email: str
name: str
created_at: datetime
class Config:
orm_mode = True
class UserProfile(BaseModel):
id: int
email: str
name: str
address: Optional[str] = None
phone: Optional[str] = None
@app.get('/users/{user_id}', response_model=UserPublic)
async def get_user(user_id: int):
user = await User.objects.get(id=user_id)
return user
This pattern ensures that even if the database query returns a complete User object with sensitive fields, only the explicitly defined fields in UserPublic are serialized and returned.
For endpoints that need different levels of detail based on authorization, use FastAPI's dependency injection to control data exposure:
from fastapi import Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
async def get_current_user_role(
session: AsyncSession = Depends(get_db),
token: str = Depends(oauth2_scheme)
):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return credentials_exception
@app.get('/users/{user_id}')
async def get_user(
user_id: int,
current_user: User = Depends(get_current_user_role)
):
user = await User.objects.get(id=user_id)
if current_user.role == 'admin':
return user
elif current_user.id == user_id:
return UserPublic.from_orm(user)
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Insufficient permissions"
)
FastAPI's response_model_exclude_unset parameter is useful for optional fields:
@app.get('/users/{user_id}', response_model=UserPublic, response_model_exclude_unset=True)
async def get_user(user_id: int):
user = await User.objects.get(id=user_id)
return user
This ensures that fields with default values (like empty strings or None) aren't included in the response, reducing unnecessary data exposure.
For comprehensive PII protection, implement a response filtering middleware:
class PIIFilterMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
async def filter_send(message):
if message['type'] == 'http.response.start':
# Check response status and headers
pass
elif message['type'] == 'http.response.body':
body = json.loads(message['body'])
filtered_body = self.filter_pii(body)
message = {
'type': 'http.response.body',
'body': json.dumps(filtered_body).encode(),
'more_body': message.get('more_body', False)
}
await send(message)
return await self.app(scope, receive, filter_send)
def filter_pii(self, data):
pii_fields = {'ssn', 'credit_card', 'password', 'ssn', 'dob'}
if isinstance(data, dict):
return {k: (self.filter_pii(v) if k not in pii_fields else '[FILTERED]')
for k, v in data.items()}
elif isinstance(data, list):
return [self.filter_pii(item) for item in data]
return data
Integrate this middleware into your FastAPI application to provide an additional layer of PII protection across all endpoints.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |