Skip to content

fix(api): cap resize amplification and correct the error contract - #37

Merged
PunGrumpy merged 7 commits into
mainfrom
advisor/002-api-hardening
Aug 30, 2026
Merged

PunGrumpy merged 7 commits into
mainfrom
advisor/002-api-hardening

Conversation

@PunGrumpy

Copy link
Copy Markdown
Owner

Why

The only cap on `/compress` output was 100 megapixels absolute, with no relation to the input. A 1x1 PNG of about 70 bytes with `maintainAspect=false&resizeWidth=10000&resizeHeight=10000` forced a ~400 MB RGBA allocation, a full Catmull-Rom rescale, and a max-compression PNG encode. The request costs the attacker nothing and holds a throttle slot for the whole encode.

Four smaller contract gaps compounded it:

  • Aspect-derived dimensions skipped the documented 16384 per-side limit. A 1x1000 source with `resizeWidth=100` produced a 100x100000 output.
  • A request whose 55s deadline expired during the encode still committed a 200, then collided with chi's deferred 504.
  • Every multipart parse failure returned 413 "too large", including malformed bodies that deserve a 400.
  • GIF input worked or failed depending on which `outputFormat` was sent, contradicting the README, and log lines carried no request ID because `Logger` registered before `RequestID`.

What changed

  • `Resize` now rejects outputs over `MaxDimension` per side and over 16x the source pixel count (new `MaxUpscaleFactor` constant). 16x pixels allows a 4x-per-side upscale, which covers the UI's resize inputs.
  • The handler checks the request context after `core.Encode`, matching the three existing checks, so an expired deadline never commits a 200.
  • `ParseMultipartForm` errors split on `*http.MaxBytesError`: oversized bodies get 413, malformed bodies get 400.
  • The `image/gif` decoder registration is gone. GIF now always fails with "only JPEG, PNG, and WebP images are supported", which matches the README.
  • `RequestID` registers before `Logger`, so request IDs appear in log lines.

The documented limits (`MaxFileSize`, `MaxPixels`, `MaxDimension`) are unchanged; the web client mirrors them.

Tests

Seven new tests: per-side and upscale-factor rejection plus a passing 4x upscale, cancellation timed to land after the encode (via a context that only reports done from its 4th `Err()` call), malformed-multipart 400, and GIF rejection using hand-built GIF bytes. The GIF test avoids importing `image/gif`, since that package's `init()` would re-register the decoder and make the test pass for the wrong reason.

Verification

`go test ./...` and `go vet ./...` pass in both Go modules; `bun run test` passes at the repo root (web 43, api, core).

@changeset-bot

changeset-bot Bot commented Aug 30, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 55851b2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pigo-api Ready Ready Preview Aug 30, 2026 2:00am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
pigo Skipped Skipped Aug 30, 2026 2:00am

@PunGrumpy
PunGrumpy merged commit d0fe068 into main Aug 30, 2026
10 checks passed
@PunGrumpy
PunGrumpy deleted the advisor/002-api-hardening branch August 30, 2026 02:34

This branch was successfully deployed

1 active and 1 inactive deployments
Preview – pigo-api 55851b2a Deployed Aug 30, 2026 by vercel[bot]
Preview – pigo 55851b2a Deployed Aug 30, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant