Build image / build-and-push (push) Successful in 6s
Security / correctness (multi-model): - Wrap the decoders in a recover (decodeSafely): a malformed HEIC/WebP that panics libheif-via-WASM or x/image/webp now fails this one request as ErrUnsupported instead of taking the process down. - Make the pixel-bomb guard overflow-safe: check each side against maxDimension BEFORE multiplying, so a header claiming ~2^32 on a side can't wrap int64(w)*int64(h) negative and slip past the pixel-count cap. - Lower maxDecodePixels 100 MP → 50 MP (~200 MB peak), still above any current phone sensor, capping the amplification a small hostile header can force. - Sentinel errors use errors.New, not fmt.Errorf without a verb. The finding 5 models agreed on: the decompression-bomb guard was UNTESTED and a stale comment implied otherwise. Added TestNormalizeRejectsPixelBomb, which crafts a ~40-byte PNG (valid IHDR + CRC) claiming a huge canvas and asserts ErrTooLarge from DecodeConfig alone, before any bitmap is allocated — covering both the per-side and the area trip. Fixed the stale comment. Error-handling / docs: - format is now "" on ALL error paths (was populated on some, empty on others). - Doc rewritten to state the actual error contract (read/encode I/O → wrapped, not a sentinel) and to stop referencing a non-existent TestNormalize / TODO. - Guard io.LimitReader's +1 against an int64 overflow at an absurd MaxBytes. Tests / provenance: - Downscale test derives its numbers from DefaultMaxDim instead of hard-coding. - Check the previously-ignored DecodeConfig error in the small-image case. - testdata/README documents where sample.heic/webp came from. Deferred to the #81 upload handler, documented in the Normalize doc: EXIF orientation (best fixed and tested with a real oriented photo end-to-end) and context cancellation (image.Decode isn't cancellable mid-decode; the caller runs it under a timeout, and the size guards keep the work finite regardless). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ