fix: give covers their own 10 MiB byte cap (#71) #112
Reference in New Issue
Block a user
Delete Branch "fix/71-cover-byte-cap"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #71.
What
Cover fetches reused
maxBodyBytes(4 MiB), the ceiling sized for series pages. Covers above it were rejected, logged, and retried every due cycle while the Series kept a monogram forever.backend/internal/latest/cover.gonow uses its ownmaxCoverBytes = 10 << 20;maxBodyBytesis untouched and still governs pages.Why 10 MiB
Live probe 2026-08-17, plain
curl, no Cloudflare challenge:The p90 already crosses the old cap, and two of the three rejected asura covers are JPEGs, not the animated GIF the issue names. 10 MiB clears 100% of the 103 sampled covers with ~18% headroom over the worst, and matches the caps GitHub (10 MB images/gifs) and Discord (10 MiB default) document for the same asset class.
The format gives no ceiling to lean on: GIF89a has no file-size field, frames are explicitly unlimited, and Go's
image/gifbounds nothing. The full reading — spec, decoders, distribution, calibration — is indocs/research/gif-maximum-byte-size.md.Memory: both cover paths buffer whole (
cover.gofetch,handlers.goserve), so worst case is 2 x cap = 20 MiB per concurrent fetch+serve pair, ~10% of the 1974 MiB VPS at ten of each.Rejected alternatives
image/gifallocates width x height per frame with no dimension guard, so a legal 65535^2 GIF asks for ~4.29 GB on a swapless host. Also mutates content-addressed bytes.Not in scope
BrowserFetcher.Image(browser.go) caps nothing at all — a real gap, but a different failure mode (CDP data URL, notio.LimitReader) on the kagane/comix path, whose covers measure small. Worth its own issue.Verification
go test ./...— 446 pass, 10 packages.TestCoverFetcherAcceptsCoverOverPageCap(new): a cover above the page cap now round-trips with its content type.TestCoverFetcherRejectsOversizedBody: re-anchored tomaxCoverBytes, so the guard is still proven, including the unknown-Content-Lengthpath.Security invariants preserved:
validateURLHTTPS/public-address gate unchanged,io.LimitReadercap still applied to every cover body with the post-read length re-check, no change to auth, CORS, or session handling.