iOfficeAI/OfficeCLI · error · ArgumentException

Image file '{path}' has extension .{ext} but magic bytes ind

Error message

Image file '{path}' has extension .{ext} but magic bytes indicate {ContentTypeName(sniffed)}. Rename or convert the file.

What it means

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.

Source

Thrown at src/officecli/Core/ImageSource.cs:85

    {
        if (!File.Exists(path))
            throw new FileNotFoundException($"Image file not found: {path}");

        var contentType = ExtensionToContentType(Path.GetExtension(path));
        var ext = Path.GetExtension(path).TrimStart('.').ToLowerInvariant();

        // Magic-byte validation for raster formats. SVG (XML) / EMF / WMF are
        // intentionally skipped: SVG has no fixed magic, EMF/WMF have weaker
        // headers and TrySniffContentType doesn't cover them. Only validate
        // formats whose first 4 bytes are stable (png/jpg/gif/bmp/tiff).
        var rasterExts = new[] { "png", "jpg", "jpeg", "gif", "bmp", "tif", "tiff" };
        if (rasterExts.Contains(ext))
        {
            var bytes = File.ReadAllBytes(path);
            if (TrySniffContentType(bytes, out var sniffed))
            {
                if (!IsCompatible(sniffed, contentType))
                    throw new ArgumentException(
                        $"Image file '{path}' has extension .{ext} but magic bytes indicate {ContentTypeName(sniffed)}. " +
                        "Rename or convert the file.");
            }
            else
            {
                throw new ArgumentException(
                    $"Image file '{path}' does not appear to be a valid {ext} file (magic bytes mismatch).");
            }
            return (new MemoryStream(bytes, writable: false), contentType);
        }

        return (File.OpenRead(path), contentType);
    }

    private static bool IsCompatible(PartTypeInfo sniffed, PartTypeInfo declared)
    {
        if (sniffed == declared) return true;
        // jpg/jpeg are the same PartTypeInfo so this collapses naturally.

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Rename the file to match its actual format (e.g. if it's really JPEG, rename .png → .jpg).
  2. Convert the file to the declared format using an image tool (e.g. 'magick photo.png photo_real.png').
  3. Verify the file's true format with 'file photo.png' or a hex dump of the first bytes, then use the correct extension.

Example fix

// before — file is JPEG but named .png
add image src='/tmp/photo.png' path='/body'

// after — rename to match actual format
mv /tmp/photo.png /tmp/photo.jpg
add image src='/tmp/photo.jpg' path='/body'
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check: verify the file's actual format matches its extension
using System.IO;
string ext = Path.GetExtension(path).TrimStart('.').ToLowerInvariant();
byte[] header = File.ReadAllBytes(path).Take(8).ToArray();
bool extMatchesMagic = (ext, header) switch
{
    ("png", var h) when h[0]==0x89 && h[1]==0x50 && h[2]==0x4E && h[3]==0x47 => true,
    ("jpg" or "jpeg", var h) when h[0]==0xFF && h[1]==0xD8 && h[2]==0xFF => true,
    ("gif", var h) when h[0]==0x47 && h[1]==0x49 && h[2]==0x46 && h[3]==0x38 => true,
    ("bmp", var h) when h[0]==0x42 && h[1]==0x4D => true,
    _ => true, // assume OK for formats we don't sniff
};
if (!extMatchesMagic) Console.Error.WriteLine($"Warning: extension .{ext} doesn't match file content");

Try / catch

try
{
    var (stream, contentType) = ImageSource.Resolve(path);
}
catch (ArgumentException ex) when (ex.Message.Contains("magic bytes indicate"))
{
    // Rename file to match actual format, then retry
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/ba01ba6542750579. Report an issue: GitHub.