Unrestricted file uploads are the intersection of CWE-434 (unrestricted upload of dangerous file type) and CWE-770 (resource exhaustion). Accepting unbounded file sizes lets attackers fill your storage quota and exhaust memory parsing the upload body. Validating file type only by extension or the caller-controlled Content-Type header is trivially bypassed: renaming exploit.php to exploit.png and setting Content-Type: image/png defeats both checks. OWASP API 2023 flags this under API4 (unrestricted resource consumption) and API8 (security misconfiguration).
High because malicious file uploads can exhaust storage, escape to arbitrary code execution if served directly, and bypass type checks that rely on attacker-controlled metadata.
Harden every upload endpoint in app/api/ with three independent controls. First, limit size at the parser level. Second, validate MIME type from actual file bytes using the file-type package — Content-Type headers are attacker-controlled and cannot be trusted. Third, enforce an allowlist:
import { fileTypeFromBuffer } from 'file-type'
const MAX_SIZE = 5 * 1024 * 1024 // 5MB
const ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp', 'application/pdf'])
const arrayBuffer = await file.arrayBuffer()
if (arrayBuffer.byteLength > MAX_SIZE) return apiError('FILE_TOO_LARGE', '...', 413)
const detected = await fileTypeFromBuffer(Buffer.from(arrayBuffer))
if (!detected || !ALLOWED_TYPES.has(detected.mime)) {
return apiError('INVALID_FILE_TYPE', '...', 415)
}
Store the file in object storage (S3, Supabase Storage, R2) under a random UUID key — never the user-supplied filename.
saas-api-design.api-security.file-upload-restrictionshighmultipart/form-data handling, FormData, multer, formidable, busboy, direct file buffer handling, or Next.js formData() calls). For each upload endpoint, check: (1) Is there a maximum file size limit enforced? (2) Is there a file type allowlist (MIME type check and/or file extension check)? (3) Is the MIME type validated from the actual file content, not just the filename or Content-Type header (which can be spoofed)?multipart/form-data handling, no multer/formidable/busboy dependencies, no FormData processing in any route handler.POST /api/upload has no file size limit. Identify which upload routes lack restrictions (e.g., "POST /api/upload has no file size limit; type is validated only by extension, not MIME content"). Max 500 chars.app/api/ with three controls. First, enforce a maximum file size at the parser level (e.g., multer({ limits: { fileSize: 5 * 1024 * 1024 } }) for 5MB). Second, validate the MIME type from the actual file content — use the file-type npm package which reads the file's magic bytes rather than trusting the declared Content-Type. Third, maintain an explicit allowlist of permitted types (e.g., ['image/jpeg', 'image/png', 'image/webp', 'application/pdf']) and reject anything not on it. Store uploaded files in cloud object storage (S3, Supabase Storage, Cloudflare R2) rather than the local filesystem, and give them random names (not user-supplied names) to prevent path traversal.