affaan-m/ECC · warning · ValidationError

File extension does not match file content.

Error message

File extension does not match file content.

What it means

After the MIME passes the allowlist, validate_file_type() cross-checks that the uploaded file's extension is one of those associated with the detected MIME (via MIME_TO_EXTENSIONS). This catches content/extension mismatches — e.g. a PDF renamed to .png, or a jpeg with a .gif extension — and is a defense against polyglot/mislabel attacks layered on top of the magic-byte check. A legitimate but unusual extension (e.g. .jpe for jpeg) will also trip this until added to the map.

Source

Thrown at skills/django-security/SKILL.md:419

MIME_TO_EXTENSIONS = {
    'image/jpeg': {'.jpg', '.jpeg'},
    'image/png': {'.png'},
    'image/gif': {'.gif'},
    'application/pdf': {'.pdf'},
}

def validate_file_type(value):
    """Validate file type using magic bytes and cross-check extension."""
    mime = magic.from_buffer(value.read(2048), mime=True)
    value.seek(0)

    if mime not in ALLOWED_MIMES:
        raise ValidationError('Unsupported file type.')

    ext = os.path.splitext(value.name)[1].lower()
    if ext not in MIME_TO_EXTENSIONS.get(mime, set()):
        raise ValidationError('File extension does not match file content.')

def validate_file_size(value):
    """Validate file size (max 5MB)."""
    if value.size > 5 * 1024 * 1024:
        raise ValidationError('File too large. Max size is 5MB.')

# models.py
class Document(models.Model):
    file = models.FileField(
        upload_to='documents/',
        validators=[validate_file_type, validate_file_size]
    )

```

For environments where installing libmagic is difficult (e.g., minimal containers),
use the pure-Python `filetype` package as an alternative:

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Rename the file so its extension matches its actual content type before upload.
  2. If the extension is legitimately valid for that MIME, add it to MIME_TO_EXTENSIONS (e.g. add '.jpe' to image/jpeg).
  3. Normalize extensions server-side from the detected MIME instead of rejecting.
  4. Reject with a clear user-facing message asking to re-save with the correct extension.

Example fix

// before — user uploads photo.jpg that is actually a PDF
# raises: File extension does not match file content.

// after — normalize extension from detected MIME server-side
import os
MIME_TO_EXT = {'image/jpeg': '.jpg', 'image/png': '.png', 'image/gif': '.gif', 'application/pdf': '.pdf'}

def normalize_extension(value, detected_mime: str) -> None:
    expected = MIME_TO_EXT.get(detected_mime)
    if expected:
        value.name = os.path.splitext(value.name)[0] + expected
Defensive patterns

Strategy: validation

Validate before calling

import os, magic

MIME_TO_EXTENSIONS = {
    'image/jpeg': {'.jpg', '.jpeg'},
    'image/png': {'.png'},
    'image/gif': {'.gif'},
    'application/pdf': {'.pdf'},
}

def extension_matches_content(path: str, filename: str) -> bool:
    with open(path, "rb") as f:
        mime = magic.from_buffer(f.read(2048), mime=True)
    ext = os.path.splitext(filename)[1].lower()
    return ext in MIME_TO_EXTENSIONS.get(mime, set())

Type guard

from django.core.exceptions import ValidationError

def is_extension_mismatch(exc: BaseException) -> bool:
    return isinstance(exc, ValidationError) and "extension does not match" in str(exc)

Try / catch

from django.core.exceptions import ValidationError

try:
    validate_file_type(uploaded)
except ValidationError as e:
    if "extension does not match" in str(e):
        return JsonResponse({"error": "Rename file to match its content type"}, status=400)
    raise

Prevention

When it happens

Trigger: A file whose magic bytes say image/jpeg but whose name ends in .png; a PDF renamed to .jpg; any case where os.path.splitext(name) yields an extension not in MIME_TO_EXTENSIONS[mime].

Common situations: Users renaming files to bypass extension filters; automated conversions that keep the old extension; polyglot upload attempts; a legitimate file with an unusual-but-valid extension not present in the map.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/172a8b5ef2cd8958. Report an issue: GitHub.