This is an issue when a FastAPI endpoint accepts file uploads (using File()) and also receives structured data through
Body() parameters or query parameters (via Depends() without proper form handling). This pattern causes either validation
errors at runtime or exposes sensitive information in URLs and logs.
When building web APIs with FastAPI, developers often need to create endpoints that accept both file uploads and structured data. However, the way this data is transmitted requires careful consideration due to HTTP protocol constraints and security implications.
HTTP requests can encode data in different ways, specified by the Content-Type header:
application/json - Used for JSON data in the request body multipart/form-data - Required for file uploads, encodes both files and form fields ? character When a FastAPI endpoint includes File() parameters, the client must send the request using multipart/form-data encoding.
This creates a fundamental incompatibility: you cannot mix Body() parameters (which expect application/json) with
File() parameters in the same endpoint.
If you declare both Body() and File() parameters in the same endpoint, FastAPI cannot properly parse the request. The
framework expects JSON in the body when it sees Body() parameters, but receives form-encoded data instead when files are included. This
results in validation errors like "value is not a valid dict" or similar type mismatches.
Some developers work around the technical constraint by passing structured data through query parameters using Depends() with a
Pydantic model. While this avoids the encoding conflict, it creates a serious security vulnerability.
Query parameters appear in the URL itself, which means they are:
If the structured data contains sensitive information (user credentials, personal data, tokens, etc.), this exposure creates a significant security risk. An attacker with access to server logs, browser history, or network traffic can extract this sensitive information.
Form data transmitted via multipart/form-data encoding:
The challenge is that form data is transmitted as strings, not as structured JSON objects. This is where Pydantic’s custom validators become essential - they allow you to parse JSON strings from form fields while maintaining type safety and validation.
When structured data containing sensitive information is passed through query parameters, it becomes visible in multiple locations:
This exposure can lead to unauthorized access to user accounts, personal information disclosure, or compliance violations (GDPR, HIPAA, PCI-DSS).
When Body() and File() parameters are mixed incorrectly, the application will fail at runtime with validation errors.
Users will be unable to complete file upload operations, resulting in:
Replace Body() parameters with Form() parameters and add a Pydantic validator to parse JSON strings. This ensures
compatibility with file uploads while maintaining type safety.
@router.post("/upload")
async def create_policy(
countryId: str = Body(...), # Noncompliant
policyDetails: List[dict] = Body(...), # Noncompliant
files: List[UploadFile] = File(...)
):
return {"status": "ok"}
class PolicyData(BaseModel):
countryId: str
policyDetails: List[dict]
@model_validator(mode='before')
@classmethod
def validate_to_json(cls, value):
if isinstance(value, str):
return cls(**json.loads(value))
return value
@router.post("/upload")
async def create_policy(
data: PolicyData = Form(...),
files: List[UploadFile] = File(...)
):
return {"status": "ok"}
Replace Depends() with Form() when passing structured data alongside file uploads. Use a custom validator to parse the
JSON string from the form field.
@app.post("/submit")
def submit(
base: Base = Depends(), # Noncompliant - data exposed in query parameters
files: List[UploadFile] = File(...)
):
return {"JSON Payload": base, "Filenames": [file.filename for file in files]}
class Base(BaseModel):
countryId: str
sensitiveData: str
@model_validator(mode='before')
@classmethod
def validate_to_json(cls, value):
if isinstance(value, str):
return cls(**json.loads(value))
return value
@app.post("/submit")
def submit(
base: Base = Form(...),
files: List[UploadFile] = File(...)
):
return {"JSON Payload": base, "Filenames": [file.filename for file in files]}
If you need to accept complex nested structures, create a dependency function that reads from Form() and performs validation with
proper error handling.
@app.post("/data")
async def upload_data(
config: DataConfiguration = Depends(), # Noncompliant
csvFile: UploadFile = File(...)
):
pass
from fastapi import HTTPException, status
from fastapi.encoders import jsonable_encoder
from pydantic import ValidationError
def parse_config(data: str = Form(...)) -> DataConfiguration:
try:
return DataConfiguration.model_validate_json(data)
except ValidationError as e:
raise HTTPException(
detail=jsonable_encoder(e.errors()),
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
)
@app.post("/data")
async def upload_data(
config: DataConfiguration = Depends(parse_config),
csvFile: UploadFile = File(...)
):
pass