Table of Contents

Security

Egami is built to open files from people you do not trust: uploads, email attachments, crawled images.

Limits

EgamiContext strict = EgamiContext.Default with
{
    Limits = SafetyLimits.Default with { MaxPixels = 25_000_000, MaxTotalBytes = 256L * 1024 * 1024 },
};

try
{
    // Read the header first: cheap, and it rejects what you do not want before any pixel memory is used.
    RasterInfo info = Raster.Probe(upload, strict);
    if (info.Width > 8000 || info.Height > 8000)
    {
        throw new InvalidDataException("Too large.");
    }

    using Raster image = Raster.Load(upload, strict, new ReaderOptions { Recovery = RecoveryMode.Strict });
    Console.WriteLine($"{info.Format.Name} {image.Width} × {image.Height}");
}
catch (UnsupportedFormatException)
{
    Console.WriteLine("Not an image format we accept.");
}
catch (LimitExceededException e)
{
    Console.WriteLine($"Over the {e.LimitName} limit.");
}
catch (CorruptDataException e)
{
    Console.WriteLine($"Damaged at byte {e.Offset}.");
}
Limit Default Guards against
MaxWidth, MaxHeight 65,535 Absurd dimensions in a header
MaxPixels 268 million Decompression bombs: a tiny file that claims a huge image
MaxTotalBytes 2 GB Everything one load or chain holds at once, frames and scratch included
MaxFrames 10,000 Animations with endless frames
MaxMetadataBytes 16 MB Oversized EXIF, XMP, ICC and text blocks

Every reader checks sizes from the header before it allocates pixel memory; a breached limit throws LimitExceededException with the limit's name, before any large allocation.

Damaged files

By default readers are lenient: a truncated or damaged file returns the rows decoded so far, the rest transparent black (or black), and lists what happened in Metadata.Warnings, each with a stable id; a diagnostics handler gets the same list as each load ends. A header that makes no sense always throws. RecoveryMode.Strict throws CorruptDataException on any problem instead, with the byte offset.

All three exceptions derive from EgamiException; anything else thrown by a reader is a bug.

Fuzzing

Every reader is fuzzed with AFL++ through SharpFuzz, and every input a fuzzer ever found is kept as a regression test. The harness runs with MaxTotalBytes at 256 MB and treats any exception other than EgamiException, a hang or an out-of-memory as a finding.

Reporting a vulnerability

Please report security issues privately, as SECURITY.md describes.

Recipes

Short answers to common tasks in this area. Each one compiles and runs in Egami's test run.

Use from ASP.NET Core

// In an ASP.NET Core endpoint: `upload` is IFormFile.OpenReadStream(), `response` is HttpResponse.Body.
EgamiContext limits = EgamiContext.Default with
{
    Limits = SafetyLimits.Default with { MaxPixels = 40_000_000, MaxTotalBytes = 512L * 1024 * 1024 },
};

using Raster image = await Raster.LoadAsync(upload, limits, new ReaderOptions { MinimumSize = new Size(256, 256) }, Ct);
image.Apply(ops => ops.AutoOrient().Cover(256, 256), Ct);
await image.SaveAsync(response, JpegFormat.Info, new JpegWriterOptions { Quality = 85, StripMetadata = true }, Ct);

Egami has no ASP.NET Core dependency: it reads and writes any Stream. The async calls read the upload into a pooled buffer, counted against MaxTotalBytes, so a slow client never holds a thread.

Run it

samples/Security is a console app that does what this guide shows on the bundled images: dotnet run --project samples/Security writes its results to ./output.