{"record":{"id":"ba01ba6542750579","repo":"iOfficeAI/OfficeCLI","slug":"image-file-path-has-extension-ext-but-magic","errorCode":null,"errorMessage":"Image file '{path}' has extension .{ext} but magic bytes indicate {ContentTypeName(sniffed)}. Rename or convert the file.","messagePattern":"Image file '(.+?)' has extension \\.(.+?) but magic bytes indicate (.+?)\\. Rename or convert the file\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/ImageSource.cs","lineNumber":85,"sourceCode":"    {\n        if (!File.Exists(path))\n            throw new FileNotFoundException($\"Image file not found: {path}\");\n\n        var contentType = ExtensionToContentType(Path.GetExtension(path));\n        var ext = Path.GetExtension(path).TrimStart('.').ToLowerInvariant();\n\n        // Magic-byte validation for raster formats. SVG (XML) / EMF / WMF are\n        // intentionally skipped: SVG has no fixed magic, EMF/WMF have weaker\n        // headers and TrySniffContentType doesn't cover them. Only validate\n        // formats whose first 4 bytes are stable (png/jpg/gif/bmp/tiff).\n        var rasterExts = new[] { \"png\", \"jpg\", \"jpeg\", \"gif\", \"bmp\", \"tif\", \"tiff\" };\n        if (rasterExts.Contains(ext))\n        {\n            var bytes = File.ReadAllBytes(path);\n            if (TrySniffContentType(bytes, out var sniffed))\n            {\n                if (!IsCompatible(sniffed, contentType))\n                    throw new ArgumentException(\n                        $\"Image file '{path}' has extension .{ext} but magic bytes indicate {ContentTypeName(sniffed)}. \" +\n                        \"Rename or convert the file.\");\n            }\n            else\n            {\n                throw new ArgumentException(\n                    $\"Image file '{path}' does not appear to be a valid {ext} file (magic bytes mismatch).\");\n            }\n            return (new MemoryStream(bytes, writable: false), contentType);\n        }\n\n        return (File.OpenRead(path), contentType);\n    }\n\n    private static bool IsCompatible(PartTypeInfo sniffed, PartTypeInfo declared)\n    {\n        if (sniffed == declared) return true;\n        // jpg/jpeg are the same PartTypeInfo so this collapses naturally.","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/ImageSource.cs#L67-L103","documentation":"Thrown by ImageSource.ResolveFile during magic-byte validation of raster formats (png, jpg, gif, bmp, tiff). The file's first bytes are sniffed and compared against the content type declared by the file extension; if they don't match (e.g. a .png file whose magic bytes are JPEG's FF D8 FF), the error is raised. This prevents embedding a corrupt or deliberately mislabeled image that would render incorrectly or cause Office to reject the document on reopen. SVG/EMF/WMF are intentionally skipped because they lack stable magic bytes.","triggerScenarios":"Passing a file named 'photo.png' that is actually a JPEG, or 'image.gif' that contains PNG data. The sniff succeeds (TrySniffContentType returns true) but IsCompatible(sniffed, declared) returns false because the sniffed format differs from what the extension declares. This is common with files that were renamed rather than converted.","commonSituations":"A file downloaded from the web where the server's content-type disagreed with the downloaded filename extension. A user who renamed 'photo.jpg' to 'photo.png' without converting the actual bytes. A pipeline that applies the wrong extension to a generated image. An adversarial input where the extension is spoofed to bypass a naive extension-based filter.","solutions":["Rename the file to match its actual format (e.g. if it's really JPEG, rename .png → .jpg).","Convert the file to the declared format using an image tool (e.g. 'magick photo.png photo_real.png').","Verify the file's true format with 'file photo.png' or a hex dump of the first bytes, then use the correct extension."],"exampleFix":"// before — file is JPEG but named .png\nadd image src='/tmp/photo.png' path='/body'\n\n// after — rename to match actual format\nmv /tmp/photo.png /tmp/photo.jpg\nadd image src='/tmp/photo.jpg' path='/body'","handlingStrategy":"validation","validationCode":"// Pre-check: verify the file's actual format matches its extension\nusing System.IO;\nstring ext = Path.GetExtension(path).TrimStart('.').ToLowerInvariant();\nbyte[] header = File.ReadAllBytes(path).Take(8).ToArray();\nbool extMatchesMagic = (ext, header) switch\n{\n    (\"png\", var h) when h[0]==0x89 && h[1]==0x50 && h[2]==0x4E && h[3]==0x47 => true,\n    (\"jpg\" or \"jpeg\", var h) when h[0]==0xFF && h[1]==0xD8 && h[2]==0xFF => true,\n    (\"gif\", var h) when h[0]==0x47 && h[1]==0x49 && h[2]==0x46 && h[3]==0x38 => true,\n    (\"bmp\", var h) when h[0]==0x42 && h[1]==0x4D => true,\n    _ => true, // assume OK for formats we don't sniff\n};\nif (!extMatchesMagic) Console.Error.WriteLine($\"Warning: extension .{ext} doesn't match file content\");","typeGuard":null,"tryCatchPattern":"try\n{\n    var (stream, contentType) = ImageSource.Resolve(path);\n}\ncatch (ArgumentException ex) when (ex.Message.Contains(\"magic bytes indicate\"))\n{\n    // Rename file to match actual format, then retry\n}","preventionTips":["Always convert images properly rather than renaming file extensions.","After downloading an image, verify its actual format with 'file' command or magic-byte sniffing before embedding.","In automated pipelines, normalize file extensions to match content using a detection step."],"tags":["image","magic-bytes","format-mismatch","validation","security"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}