Repository navigation
Description
Summary
An image at (or just under) the client's advertised 10 MiB maximum passes the
web UI's size check but is rejected by the server, because the client's limit
applies to the file while the server's identical 10 MiB limit applies to
the whole multipart request body, whose encoding adds overhead the file
budget does not account for.
Evidence (verified on main 26c54292, 2026-10-23)
- Client:
pkgs/id/web/src/image-upload.ts:34—MAX_IMAGE_SIZE = 10 * 1024 * 1024;
paste/drop handlers skip a file only whenfile.size > MAX_IMAGE_SIZE
(image-upload.ts:170and:198), so a file of exactly 10 MiB is uploaded.
The constant is pinned byimage-upload.test.ts:43-49. - Server:
pkgs/id/src/web/routes.rs:265— the whole router is layered with
DefaultBodyLimit::max(10 * 1024 * 1024).upload_handler
(routes.rs:1272) has no size check of its own, so the body limit is the
only server-side bound and it counts every byte of the multipart envelope. - Measured with Node's
FormData/Request(the same encoding the browser
produces) for a file namedphoto.png:- file size 10,485,760 → multipart body 10,485,930 bytes (170 bytes of
overhead: boundary,Content-Dispositionwith the filename, part
Content-Type, closing boundary). Over the server limit → rejected. - file size 10,485,660 (100 bytes under the advertised max) → body
10,485,830 → still rejected. - file size 10,485,560 → body 10,485,730 → accepted.
So the effective maximum is ≈200 bytes below the advertised one, and it
shrinks further with longer filenames. The user sees only a generic
Upload failed (…)error fromuploadImageFilewith no hint that a file
the UI explicitly allowed is "too large".
- file size 10,485,760 → multipart body 10,485,930 bytes (170 bytes of
Why it matters
The two sides disagree about what the limit measures, so the boundary of the
documented feature is broken in exactly the range a user who checks the limit
will hit: a 10 MiB image (a common phone-photo size class) is promised by the
client and refused by the server. The failure mode is silent/confusing rather
than a clear "image too large" message.
Acceptance criteria
- A file of exactly
MAX_IMAGE_SIZEbytes (10 MiB) with a typical
filename uploads successfully end to end, or the client limit is
lowered/documented so the largest client-accepted file always fits the
server body limit (including multipart overhead for long filenames). - The server rejects an over-limit file with a clear size-related error
(not a generic multipart/body error leaking throughupload_handler). - A test pins the relationship between the two limits (e.g. a Rust test
asserting the upload route's effective body limit exceeds the client
MAX_IMAGE_SIZEplus a multipart-overhead allowance, or a client test
for the adjusted constant) so the two cannot drift back into conflict. - Existing
image-uploadweb tests and the Rust web route tests still pass.
Suggested fix direction: raise the body limit for the /api/upload route
only (route-scoped DefaultBodyLimit above 10 MiB + overhead, e.g. 11 MiB)
and/or add an explicit file-size check in upload_handler returning
413 with a clear message — rather than raising the global router limit,
which also guards /api/save and the other POST routes.
Activity