One client can keep every image processing slot busy: the image routes have no limit per client #236

Open
opened 2026-10-12 10:56:10 +02:00 by clawbot · 0 comments
Collaborator

The image routes (/v1/image/ and /v1/e/) have no limit per client. TODO.md names this as the next step ("P2: security: per-IP rate limiting on the image routes"), and no issue tracks it.

Why it is a defect: with allowlist_hosts set, anyone can request an allowlisted image at any size, format, quality and fit without a signature, and each new combination is fetched or read, decoded and encoded. max_concurrent_processing caps the work done at once, but one client can keep every processing slot busy, so every other client's cache misses wait behind it until their downstream_timeout ends.

Design (recommended, built unless the owner rules otherwise)

  • One new setting, image_requests_per_minute (PIXA_IMAGE_REQUESTS_PER_MINUTE): how many image requests one client may make per minute. Default 600. 0 turns the limit off. A negative or non-integer value stops pixad at startup, as other settings do.
  • The limit applies to GET and HEAD on both image routes, through the existing RateLimit middleware in internal/middleware/middleware.go, so a client is told apart exactly as for the login limit (trusted_proxies, an IPv6 client by its /64). A request over the limit gets 429 with Retry-After. Cache hits count too; the limit is about requests, not work.
  • A browser's CORS preflight (OPTIONS) is not counted.
  • README.md documents the setting next to the login limit, and says that behind a CDN or proxy whose addresses are not in trusted_proxies every request counts as the same client, so such a deployment raises the limit or sets 0. config.example.yml and the settings table name it.

Definition of done

  • The setting exists as above, read and validated like the other settings, with its default and 0 documented.
  • New tests, through the router: the request after the limit within a minute gets 429 with Retry-After; two clients are counted apart; 0 lets every request through; an invalid value stops startup. Existing tests are not changed (CLAUDE.md).
  • TODO.md: the next step moves to Completed Steps, and the next item of Future Steps becomes the next step.
  • script/cibuild passes; one PR against next, reviewed by an independent reviewer.

Model: opus-5-5

The image routes (`/v1/image/` and `/v1/e/`) have no limit per client. `TODO.md` names this as the next step ("P2: security: per-IP rate limiting on the image routes"), and no issue tracks it. Why it is a defect: with `allowlist_hosts` set, anyone can request an allowlisted image at any size, format, quality and fit without a signature, and each new combination is fetched or read, decoded and encoded. `max_concurrent_processing` caps the work done at once, but one client can keep every processing slot busy, so every other client's cache misses wait behind it until their `downstream_timeout` ends. ## Design (recommended, built unless the owner rules otherwise) - One new setting, `image_requests_per_minute` (`PIXA_IMAGE_REQUESTS_PER_MINUTE`): how many image requests one client may make per minute. Default 600. `0` turns the limit off. A negative or non-integer value stops pixad at startup, as other settings do. - The limit applies to GET and HEAD on both image routes, through the existing `RateLimit` middleware in `internal/middleware/middleware.go`, so a client is told apart exactly as for the login limit (`trusted_proxies`, an IPv6 client by its /64). A request over the limit gets 429 with `Retry-After`. Cache hits count too; the limit is about requests, not work. - A browser's CORS preflight (OPTIONS) is not counted. - `README.md` documents the setting next to the login limit, and says that behind a CDN or proxy whose addresses are not in `trusted_proxies` every request counts as the same client, so such a deployment raises the limit or sets `0`. `config.example.yml` and the settings table name it. ## Definition of done - The setting exists as above, read and validated like the other settings, with its default and `0` documented. - New tests, through the router: the request after the limit within a minute gets 429 with `Retry-After`; two clients are counted apart; `0` lets every request through; an invalid value stops startup. Existing tests are not changed (`CLAUDE.md`). - `TODO.md`: the next step moves to Completed Steps, and the next item of Future Steps becomes the next step. - `script/cibuild` passes; one PR against `next`, reviewed by an independent reviewer. Model: opus-5-5
clawbot self-assigned this 2026-10-12 10:56:10 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/pixa#236