Skip to content

Web Form File Validation Implementation Checklist

Category: Security / Implementation

Implementing file upload functionality involves numerous security considerations and many easily overlooked pitfalls. This article explains a checklist for implementing file uploads that operate safely in production environments.

Recommended file validation order Safe order: light to heavy checks 1. File size bytes <= max NG → reject immediately 2. Extension .jpg / .png ... NG → reject immediately 3. MIME type finfo / mime_content_type NG → reject immediately 4. Magic bytes FF D8 FF (JPEG) ... NG → reject immediately 5. Content scan ClamAV / VirusTotal NG → reject immediately
Fig 1: Run lightweight validations first and reject early

Checklist Overview

This checklist focuses on backend (server-side) validation. Frontend validation is implemented as a supplementary UX improvement, but it provides no security guarantees.

1. File size validation

  • Upload limit is defined in bytes (no confusion between MB and MiB)
  • For PHP, both upload_max_filesize and post_max_size are configured
  • For Nginx, client_max_body_size includes an allowance for multipart overhead
  • Error handling is in place for when $_FILES['file']['error'] is UPLOAD_ERR_INI_SIZE / UPLOAD_ERR_FORM_SIZE
  • Minimum file size check is in place (excluding 0-byte files)
 $maxBytes) {
        throw new \RuntimeException(sprintf(
            'ファイルサイズ(%s)が上限(%s)を超えています',
            number_format($file['size']),
            number_format($maxBytes)
        ));
    }
}

2. File format validation (MIME type)

  • The Content-Type ($_FILES['file']['type']) sent from the client is not trusted
  • Server-side MIME type validation is performed using finfo / mime_content_type()
  • A whitelist of allowed MIME types is defined
file($file['tmp_name']);

if (!in_array($mimeType, $allowed, true)) {
    throw new \RuntimeException('許可されていないファイル形式です: ' . $mimeType);
}

3. File extension validation

  • A whitelist of file extensions is defined (whitelist, not blacklist)
  • Double extensions (e.g., shell.php.jpg) are detected and rejected
  • Case normalization is applied during validation (treating .JPG and .jpg as the same)
 2) {
    throw new \RuntimeException('不正なファイル名です');
}

$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) {
    throw new \RuntimeException('許可されていない拡張子です: ' . $ext);
}

4. Magic byte (file signature) validation

  • Magic bytes are validated for critical files (e.g., excluding executable files)

5. Safe Handling of Save Destination and File Name

  • The destination directory is outside the web root (or controlled via XSendFile/X-Accel-Redirect)
  • Saved file names are randomly generated (e.g., UUID) and not the original file name
  • Path traversal (e.g., ../../../etc/passwd) is eliminated through validation
  • The destination directory does not have PHP execution permission (PHP processing disabled via .htaccess or Nginx configuration)

6. Error Handling and Response

  • Appropriate HTTP status codes (200/201) are returned on successful upload
  • 413 Payload Too Large is returned when size is exceeded
  • 422 Unprocessable Entity is returned for invalid file formats
  • Error messages do not contain server internal information (paths, versions, etc.)

7. Test Cases

After implementation, run the following test cases to verify the behavior. You can use the test files available in DevLab.

Test casesExpected resultFiles to use
File exactly at the limitSuccessThreshold Files
File exceeding the limit by 1 byte413 errorThreshold Files
Empty file with 0 bytesValidation ErrorManual Creation
File with spoofed extension (PHP masked as .jpg)MIME ErrorBroken files
Corrupted header fileValidation ErrorBroken files

Summary

Implementing secure file uploads requires validation across multiple layers. In particular, be sure to implement the following three points.

  1. MIME type validation on server side (finfo usage) — Do not trust client declarations
  2. Save with random filename — Do not use the original filename
  3. Disable PHP execution in the upload directory — Prevent scripts from executing in the upload directory

❓ Frequently Asked Questions

What is the minimum a file upload validation must do?
Three things at minimum: validate the MIME type server-side with finfo rather than trusting what the client claims; save under a randomly generated name instead of the original one; and disable PHP execution in the upload directory so nothing dropped there can run.
Why is checking the file extension not enough?
Extensions are trivially forged, so they will not catch a malicious file such as a double extension like shell.php.jpg. Combine the extension whitelist with finfo MIME validation and a magic-byte check on the file header.
Which HTTP status codes are appropriate for file uploads?
Return 200 or 201 on success, 413 Payload Too Large when the file exceeds the limit, and 422 Unprocessable Entity for an invalid format. Keep server internals — paths, versions — out of the error message.