๐Ÿ“ API

Secure file uploads: size, type, names, and storage

P
Author
PyLand Team
๐Ÿ“…
Published
18.09.2026
โฑ๏ธ
Reading time
1 min
๐Ÿ‘๏ธ
Views
3
๐ŸŒฟ
Level
Medium

Choosing a photo looks simple: the browser sends a file and the server stores it.
However, its name, declared type, and bytes all came from the client, so the server
cannot trust them automatically.

A safe upload is not one complicated check but a few understandable filters. Validate
the file first, generate a safe name second, and only then pass it to local or cloud
storage.

Layered validation

Think of this as a checkpoint: each check answers one question. The resulting code is
easier to read, test, and reuse in another project.

For an image, check in this order:

  1. a file was actually supplied;
  2. the declared MIME type is allowlisted;
  3. the bytes read do not exceed the limit;
  4. signature and decoding confirm the format;
  5. the server generates the storage key instead of trusting filename.

Never join filename="../secret.txt" to the upload directory. Generate a key:

from uuid import uuid4

key = f"images/{uuid4().hex}.webp"

FastAPI UploadFile

UploadFile requires python-multipart. It provides metadata and a file interface,
and can spool larger data from memory to disk.

from fastapi import HTTPException, UploadFile

MAX_BYTES = 5 * 1024 * 1024
ALLOWED_TYPES = {"image/jpeg", "image/png", "image/webp"}

async def read_limited(upload: UploadFile) -> bytes:
    if upload.content_type not in ALLOWED_TYPES:
        raise HTTPException(415, "Unsupported image type")
    data = await upload.read(MAX_BYTES + 1)
    if len(data) > MAX_BYTES:
        raise HTTPException(413, "Image is too large")
    if not data:
        raise HTTPException(400, "Image is empty")
    return data

Then verify magic bytes or decode the image with a suitable library. Never trust an
extension alone.

The extra byte in MAX_BYTES + 1 reveals an exceeded limit without reading the whole
file. For very large uploads, read smaller chunks instead.

Store uploads outside templates and executable code. Authorize private downloads,
delete objects by their saved key, and consider a matching request-body limit at the
reverse proxy. Test valid, empty, oversized, disguised, and ../cover.png uploads.

Begin with the happy path and change exactly one condition per test. Responses 415
for type, 413 for size, and 400 for an empty file make each rejection clear.

Official references

Your reaction to the article

๐Ÿ’ฌ Comments (0)

๐Ÿ” Sign in to leave a comment
๐Ÿšช Login
๐Ÿ’ญ

No comments yet

Be the first to share your opinion about this article!

๐Ÿ”— Similar

Similar articles

Continue learning with these materials

๐Ÿ“

S3-compatible object storage: buckets, keys, and โ€ฆ

A regular folder works on one computer. After deployment it is unreliable: a container can...

๐Ÿ“… 18.09.2026 ๐Ÿ‘๏ธ 2
๐Ÿ“

Cookie sessions and CSRF in server-rendered web aโ€ฆ

A cookie session connects browser requests to a user. A signed cookie prevents undetected modification,...

๐Ÿ“… 18.09.2026 ๐Ÿ‘๏ธ 4
๐Ÿ“

REST API Design Principles

Covered topics: Resources and URLs, HTTP methods, Nested resources, Response codes.

๐Ÿ“… 30.06.2026 ๐Ÿ‘๏ธ 349

Did you like the article?

Subscribe to our updates and be the first to receive new articles. Grow with PyLand!