mime_content_type()
debt(d5/e3/b3/t9)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and psalm as tools, and the code_pattern explicitly calls out '$_FILES["type"]' usage and extension-only checks. These are not default linter catches — they require configured SAST rules (semgrep patterns) or static analysis (psalm), placing this squarely at d5.
Closest to 'simple parameterised fix' (e3). The quick_fix states replacing client-supplied MIME with finfo_file() or mime_content_type() — a small, localised substitution in the upload-handling code. It's more than a one-liner because you need to also remove the $_FILES['type'] reliance and potentially add extension cross-checking, but it stays within a single component.
Closest to 'localised tax' (b3). The applies_to contexts are web and cli, but in practice this pattern is confined to file-upload handling logic. It doesn't spread across the whole codebase — only the upload validation component pays the tax. Future maintainers of upload code must be aware, but the rest of the codebase is unaffected.
Closest to 'catastrophic trap' (t9). The misconception field is explicit: '$_FILES["type"] is sufficient for file validation' — a belief nearly every developer holds on first encounter because the field name suggests it is the file's MIME type. In reality it is fully attacker-controlled. The 'obvious' way (reading the provided MIME field) is always wrong for security purposes, matching the t9 anchor perfectly.
Also Known As
TL;DR
Explanation
mime_content_type($filename) reads the file's binary content and determines its actual type using libmagic — the same library used by the Unix file command. This is critical for file upload validation because the browser-reported $_FILES['file']['type'] is completely attacker-controlled and cannot be trusted. Even extension checks can be bypassed with polyglot files. mime_content_type() on the actual tmp_name is the correct first step, combined with an allowlist of permitted MIME types.
Common Misconception
Why It Matters
Common Mistakes
- Using $_FILES['file']['type'] for MIME validation — this is the browser-reported type, fully attacker-controlled.
- Relying on the file extension alone — a PHP file named image.jpg is still PHP.
- Not checking magic bytes (file signature) for the most sensitive upload types.
- mime_content_type() being disabled in some hosting environments — use finfo_file() as the preferred alternative.
Code Examples
// Trusting browser-supplied MIME type:
$type = $_FILES['upload']['type']; // Attacker-controlled!
if (in_array($type, ['image/jpeg', 'image/png'])) {
move_uploaded_file($_FILES['upload']['tmp_name'], $dest);
}
// Safe server-side check:
$finfo = new finfo(FILEINFO_MIME_TYPE);
$type = $finfo->file($_FILES['upload']['tmp_name']);
// Detect MIME from file magic bytes — not browser Content-Type header
\$finfo = new \finfo(FILEINFO_MIME_TYPE);
\$mime = \$finfo->file(\$_FILES['upload']['tmp_name']);
// Returns: 'image/jpeg', 'application/pdf', 'text/plain', etc.
// mime_content_type() is an older alternative — same underlying libmagic
\$mime = mime_content_type(\$_FILES['upload']['tmp_name']);
// Allowlist check:
\$allowed = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
if (!in_array(\$mime, \$allowed, true)) abort(415, 'Unsupported Media Type');