Files
WealthySmart/backend/app/uploads.py
Carlos Escalante 82a7bfc4c5 Harden PDF uploads: size cap, magic-byte check, non-blocking parse
read_pdf_upload() bounds reads at 10 MB and requires the %PDF magic
before anything reaches pdftotext (SEC-06); rejections land in the
per-file errors list with the filename. Parsing now runs via
asyncio.to_thread so a slow PDF no longer blocks the event loop for up
to 30s (BE-14). Review notes: temp files were already context-managed
(BE-09 overstated) and the pension batch already commits atomically
(BE-10 overstated). 69 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:00:49 -06:00

23 lines
772 B
Python

from fastapi import UploadFile
MAX_UPLOAD_BYTES = 10 * 1024 * 1024 # 10 MB — statements are a few hundred KB
async def read_pdf_upload(
file: UploadFile, max_bytes: int = MAX_UPLOAD_BYTES
) -> bytes:
"""Read an uploaded PDF with a size cap and magic-byte check.
Raises ValueError with a user-readable, filename-prefixed message —
upload endpoints surface these in their per-file `errors` list.
"""
filename = file.filename or "unknown.pdf"
data = await file.read(max_bytes + 1)
if len(data) > max_bytes:
raise ValueError(
f"{filename}: file exceeds the {max_bytes // (1024 * 1024)} MB limit"
)
if not data.startswith(b"%PDF"):
raise ValueError(f"{filename}: not a PDF file")
return data