Why file uploads are a security boundary
A file upload endpoint accepts untrusted data, writes to infrastructure, and often triggers downstream parsing. That makes it one of the easiest places for small mistakes to become serious vulnerabilities.
Common risks include path traversal, oversized payloads, executable files, malicious archives, parser exploits, and accidental exposure of stored objects.
The safest pattern is to treat the original filename as display metadata only. Storage paths should be generated by the server, not chosen by the user.
Related context
Continue with the wider Cresnex research library
This article is part of a broader Cresnex library on cybersecurity, AI risk, online fraud, and India-specific digital trust. Use the links below to continue reading related explainers and research briefs.
A safer FastAPI upload route
The route below combines extension allowlisting, explicit size checks, random server-generated filenames, and controlled write paths. It is intentionally simple so the security decisions remain visible.
For production systems, pair this with authentication, malware scanning for risky file types, object storage permissions, and background processing instead of trusting the upload immediately.
from pathlib import Path
from uuid import uuid4
from fastapi import FastAPI, File, HTTPException, UploadFile, status
app = FastAPI()
UPLOAD_DIR = Path("/tmp/cresnex-safe-uploads").resolve()
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024
ALLOWED_EXTENSIONS = {".txt", ".json", ".csv", ".png", ".jpg", ".jpeg"}
@app.post("/api/v1/secure-upload")
async def secure_upload(file: UploadFile = File(...)):
original_name = file.filename or ""
file_ext = Path(original_name).suffix.lower()
if file_ext not in ALLOWED_EXTENSIONS:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="File extension is not allowed.",
)
content = await file.read()
if len(content) > MAX_FILE_SIZE_BYTES:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="File exceeds the 5 MB limit.",
)
safe_name = f"{uuid4().hex}{file_ext}"
destination = (UPLOAD_DIR / safe_name).resolve()
if UPLOAD_DIR not in destination.parents:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid upload path.",
)
destination.write_bytes(content)
return {
"status": "stored",
"saved_as": safe_name,
"bytes": len(content),
}Directory escape defense: do not trust filename cleanup alone
Functions that strip slashes or normalize filenames can help, but they should not be your only defense. Attackers look for double encoding, unicode edge cases, platform-specific behavior, and archive extraction quirks.
Generating a new server-side filename is stronger because the uploaded name never controls the path. Resolving the destination and checking that it remains inside the intended directory adds another guardrail.
If you later extract archives, apply the same rule to every member path before writing anything to disk.
Add Bandit SAST to catch insecure Python patterns early
Bandit is a static analysis tool for Python security. It scans source code for risky patterns such as hardcoded secrets, unsafe deserialization, shell injection, weak cryptography, and insecure temporary-file behavior.
A security linter will not replace code review, but it raises the floor. It catches issues that busy teams often miss when they are focused on feature delivery.
python -m pip install bandit
bandit -r ./backend -ll
# Optional JSON output for CI artifacts:
bandit -r ./backend -f json -o bandit-report.jsonrepos:
- repo: https://github.com/PyCQA/bandit
rev: 1.7.10
hooks:
- id: bandit
args: ["-r", "backend", "-ll"]Production upload checklist
Require authentication when uploads are user-specific. Store files outside the web root or in private object storage. Use short-lived signed URLs instead of public paths for sensitive files.
Log metadata such as user ID, file size, content type, generated name, and scan status. Avoid logging raw secrets, file contents, or personal documents.
For images and documents, consider asynchronous malware scanning, content-type sniffing, and safe transformation before making files available to other users.
