{"record":{"id":"172a8b5ef2cd8958","repo":"affaan-m/ECC","slug":"file-extension-does-not-match-file-content","errorCode":null,"errorMessage":"File extension does not match file content.","messagePattern":"File extension does not match file content\\.","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"warning","filePath":"skills/django-security/SKILL.md","lineNumber":419,"sourceCode":"\nMIME_TO_EXTENSIONS = {\n    'image/jpeg': {'.jpg', '.jpeg'},\n    'image/png': {'.png'},\n    'image/gif': {'.gif'},\n    'application/pdf': {'.pdf'},\n}\n\ndef validate_file_type(value):\n    \"\"\"Validate file type using magic bytes and cross-check extension.\"\"\"\n    mime = magic.from_buffer(value.read(2048), mime=True)\n    value.seek(0)\n\n    if mime not in ALLOWED_MIMES:\n        raise ValidationError('Unsupported file type.')\n\n    ext = os.path.splitext(value.name)[1].lower()\n    if ext not in MIME_TO_EXTENSIONS.get(mime, set()):\n        raise ValidationError('File extension does not match file content.')\n\ndef validate_file_size(value):\n    \"\"\"Validate file size (max 5MB).\"\"\"\n    if value.size > 5 * 1024 * 1024:\n        raise ValidationError('File too large. Max size is 5MB.')\n\n# models.py\nclass Document(models.Model):\n    file = models.FileField(\n        upload_to='documents/',\n        validators=[validate_file_type, validate_file_size]\n    )\n\n```\n\nFor environments where installing libmagic is difficult (e.g., minimal containers),\nuse the pure-Python `filetype` package as an alternative:\n","sourceCodeStart":401,"sourceCodeEnd":437,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/skills/django-security/SKILL.md#L401-L437","documentation":"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.","triggerScenarios":"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].","commonSituations":"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.","solutions":["Rename the file so its extension matches its actual content type before upload.","If the extension is legitimately valid for that MIME, add it to MIME_TO_EXTENSIONS (e.g. add '.jpe' to image/jpeg).","Normalize extensions server-side from the detected MIME instead of rejecting.","Reject with a clear user-facing message asking to re-save with the correct extension."],"exampleFix":"// before — user uploads photo.jpg that is actually a PDF\n# raises: File extension does not match file content.\n\n// after — normalize extension from detected MIME server-side\nimport os\nMIME_TO_EXT = {'image/jpeg': '.jpg', 'image/png': '.png', 'image/gif': '.gif', 'application/pdf': '.pdf'}\n\ndef normalize_extension(value, detected_mime: str) -> None:\n    expected = MIME_TO_EXT.get(detected_mime)\n    if expected:\n        value.name = os.path.splitext(value.name)[0] + expected","handlingStrategy":"validation","validationCode":"import os, magic\n\nMIME_TO_EXTENSIONS = {\n    'image/jpeg': {'.jpg', '.jpeg'},\n    'image/png': {'.png'},\n    'image/gif': {'.gif'},\n    'application/pdf': {'.pdf'},\n}\n\ndef extension_matches_content(path: str, filename: str) -> bool:\n    with open(path, \"rb\") as f:\n        mime = magic.from_buffer(f.read(2048), mime=True)\n    ext = os.path.splitext(filename)[1].lower()\n    return ext in MIME_TO_EXTENSIONS.get(mime, set())","typeGuard":"from django.core.exceptions import ValidationError\n\ndef is_extension_mismatch(exc: BaseException) -> bool:\n    return isinstance(exc, ValidationError) and \"extension does not match\" in str(exc)","tryCatchPattern":"from django.core.exceptions import ValidationError\n\ntry:\n    validate_file_type(uploaded)\nexcept ValidationError as e:\n    if \"extension does not match\" in str(e):\n        return JsonResponse({\"error\": \"Rename file to match its content type\"}, status=400)\n    raise","preventionTips":["Detect MIME first, then assign the canonical extension server-side.","Keep MIME_TO_EXTENSIONS exhaustive for each allowed MIME.","Treat extension/content mismatch as suspicious — log it.","Don't trust the client-supplied filename extension."],"tags":["django","file-upload","security","validation","mime","extension"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}