KAHIBARO
Discord Login Register

5.9.2. File Validation

Why File Validation Matters

Whenever a user uploads a file to your backend, you are trusting data that you did not create. This is always risky. A file might be:

File validation is the process of checking that an uploaded file matches your expectations before you store it or process it.

Always validate uploaded files on the server side, even if you have client-side checks. Never trust the client.

File validation is not only about security. It is also about correctness and user experience. If your API expects a PDF, it should reject images instead of failing later in a background job.

What To Validate

When a file arrives at your backend, there are several things you typically want to validate. Common checks include:

Check typeExamplesWhy it matters
SizeMax 5 MB, min 1 KBAvoid resource exhaustion, enforce limits
Type / MIME typeOnly image/jpeg, application/pdfEnsure correct processing and basic safety
Extension.jpg, .png, .pdfUser feedback, simple filtering
Content / magic bytesFile really is an image, not a scriptPrevent disguised files
DimensionsImage width and height limitsControl layout and storage cost
Page countPDF with max 10 pagesBusiness rules
Name / pathNo path traversal, no strange charactersPrevent directory tricks
CountMax N files per requestProtect from abuse

You rarely need all of these, but you should consciously choose which ones your API needs.

File Size Validation

Validating file size is usually the first and simplest check.

You can apply limits in three places:

  1. At the reverse proxy or web server (for example Nginx, Traefik).
  2. At the application server or framework level.
  3. In your application code.

The best practice is to enforce limits as early as possible, then verify again in your application logic.

Request-level vs file-level limits

Both are useful:

Example: Checking file size in code (Python-like pseudocode)

Imagine an API that accepts a single image file with a 2 MB limit.

python
MAX_FILE_SIZE = 2 * 1024 * 1024  # 2 MB
def validate_file_size(uploaded_file):
    # Many frameworks expose file size directly
    size = uploaded_file.size  # or len(uploaded_file.read()) in a streaming manner
    if size > MAX_FILE_SIZE:
        raise ValueError("File too large (max 2 MB).")

In a streaming environment you might read chunks and track the total. If the limit is exceeded, you stop reading and return an error.

Never read a huge file fully into memory without limits. Always enforce maximum sizes for uploads.

Validating File Type

Most web clients will send two hints about file type:

  1. MIME type (also called content type), for example image/jpeg, application/pdf.
  2. File extension, for example .jpg, .pdf.

These are only hints from the client. Both can be forged easily.

Using MIME types

Common patterns:

Example whitelist:

text
Allowed MIME types:
- image/jpeg
- image/png
- application/pdf

In code you might see:

python
ALLOWED_MIME_TYPES = {
    "image/jpeg",
    "image/png",
    "application/pdf",
}
def validate_mime_type(uploaded_file):
    if uploaded_file.content_type not in ALLOWED_MIME_TYPES:
        raise ValueError("Unsupported file type.")

This is a first line of defense, not your only check.

Checking file extensions

File extensions are useful for:

However, never rely solely on extensions.

Example check:

python
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png"}
def validate_extension(filename):
    import os
    _, ext = os.path.splitext(filename.lower())
    if ext not in ALLOWED_EXTENSIONS:
        raise ValueError("Unsupported file extension.")

Combine extension checks with MIME type and content checks to increase reliability.

Validating File Content (Magic Bytes)

The only way to be reasonably sure what a file really is, is to look at its content, especially the first bytes. These are often called magic bytes or file signatures.

For example:

File typeMagic bytes (hex)
JPEGFF D8 FF at the start
PNG89 50 4E 47 0D 0A 1A 0A
PDF%PDF- (ASCII)
ZIP50 4B 03 04

Many languages and libraries can guess the type from these bytes.

Example: Checking magic bytes in Python style

python
def is_jpeg(file_obj):
    # Remember current position
    pos = file_obj.tell()
    header = file_obj.read(3)
    # Restore position
    file_obj.seek(pos)
    return header == b"\xFF\xD8\xFF"

This can be generalized or replaced by a library that reads headers and recognizes common formats.

Never trust the MIME type or extension without at least a minimal content check for critical file types, such as user avatars or document uploads.

Image-specific Validation (Dimensions, Format)

Images are a very common upload type. They have special validation needs.

Typical checks:

Example rules for profile pictures

You might require:

Example logic (pseudocode):

python
MAX_SIZE = 2 * 1024 * 1024
MIN_WIDTH, MIN_HEIGHT = 256, 256
MAX_WIDTH, MAX_HEIGHT = 2000, 2000
def validate_profile_image(file):
    if file.size > MAX_SIZE:
        raise ValueError("Image too large (max 2 MB).")
    image = load_image(file)  # Use an image library like Pillow
    width, height = image.size
    if width < MIN_WIDTH or height < MIN_HEIGHT:
        raise ValueError("Image too small.")
    if width > MAX_WIDTH or height > MAX_HEIGHT:
        raise ValueError("Image too big.")

Many backends also resize images after validation. Validation and transformation are related but should be considered separate steps.

Validating File Names and Paths

Even if you never store the original filename, you should treat it carefully.

Risks include:

Never use a user-provided filename as a filesystem path directly.

Safe handling approach

  1. Ignore the directory part of the filename.
  2. Extract only the last component.
  3. Generate your own internal name, for example a UUID.

Example approach:

python
import os
import uuid
def sanitize_filename(original_name):
    # Get only the base name (strip directories)
    base_name = os.path.basename(original_name)
    # Replace spaces and risky characters if you intend to keep it
    safe_name = base_name.replace(" ", "_")
    return safe_name
def generate_storage_name(original_name):
    _, ext = os.path.splitext(original_name)
    return f"{uuid.uuid4().hex}{ext.lower()}"

Use generate_storage_name to name files on disk or object storage, not the original user filename.

Never trust user-provided file paths or directory names. Always control where files are written.

Validating Number of Files

If your endpoint accepts multiple files, validate:

Example rules:

Pseudocode:

python
MAX_FILES = 10
MAX_TOTAL_SIZE = 20 * 1024 * 1024  # 20 MB
def validate_multiple_files(files):
    if len(files) > MAX_FILES:
        raise ValueError("Too many files.")
    total_size = sum(f.size for f in files)
    if total_size > MAX_TOTAL_SIZE:
        raise ValueError("Total upload size too large.")

This limit helps protect your backend and keeps your storage predictable.

Business Rule Validation

Besides technical checks, you often need business-specific rules. Some examples:

Example pattern-based validation:

python
import re
INVOICE_PATTERN = re.compile(r"^invoice-\d{4}-\d{2}-\d{2}\.pdf$")
def validate_invoice_filename(filename):
    if not INVOICE_PATTERN.match(filename):
        raise ValueError("Filename must be like invoice-YYYY-MM-DD.pdf")

These checks are specific to your project and belong in your application logic.

Error Handling and User Feedback

Good file validation is not only about rejecting bad files. It is also about returning clear, specific error messages.

Bad feedback:

Good feedback:

Typically you want to:

For example, a JSON error response:

json
{
  "error": "validation_error",
  "field": "file",
  "message": "File too large. Maximum allowed size is 5 MB.",
  "limit": 5242880
}

This makes it easy for the frontend to show meaningful messages to the user.

Security Considerations in File Validation

File validation is an important part of backend security, but it is not enough on its own. You should combine it with:

Some specific points related to validation:

  1. Never execute uploaded files. This includes scripts, binaries, or code in templates.
  2. Treat all content as untrusted. Even validated images can contain malicious payloads for image parsers.
  3. Sanitize any data extracted from files before using it elsewhere.

Validation reduces risk but does not eliminate it. Combine validation with safe storage, strict permissions, and defense in depth.

Designing a File Validation Policy

To make file validation consistent in your backend, define a clear policy per use case. For each type of upload, specify:

Put this policy into configuration so it is easy to change without editing core logic. For example, a configuration structure:

yaml
profile_image:
  max_size: 2097152        # 2 MB
  allowed_mime_types:
    - image/jpeg
    - image/png
  min_width: 256
  min_height: 256
  max_width: 2000
  max_height: 2000
invoice_pdf:
  max_size: 5242880        # 5 MB
  allowed_mime_types:
    - application/pdf
  max_pages: 20

Then implement generic validators that read from this configuration.

By doing so, you keep file validation predictable, testable, and easy to maintain as your backend grows.

Views: 7

Comments

Please login to add a comment.

Don't have an account? Register now!