Skip to content

Attachment upload buffers the entire body in memory before the size check #46

Description

@cuibonobo

Server design-review finding.

Problem

The upload handler enforces maxAttachmentBytes in two passes (src/routes/attachments.ts:17-25): a Content-Length pre-check, then a byte-length check on the fully-read body:

const data = new Uint8Array(await c.req.arrayBuffer());
if (data.byteLength > maxAttachmentBytes) { ... 413 }

The pre-check only constrains honest clients — Content-Length is optional (chunked transfer encoding has none) and unverified. The real check runs after arrayBuffer() has already read the whole request into memory. So:

  • A client using chunked encoding can stream an arbitrarily large body; the process buffers all of it before deciding it's too big. A handful of concurrent oversized uploads exhausts memory — the JSON routes got bodyLimit for exactly this (Security: No rate limiting, no JSON body size limit, unbounded query limit parameter #14), but the attachment route's "own limit" is enforced too late to protect anything.
  • Even legitimate traffic holds the full payload (default up to 50 MB) per in-flight upload, plus a second copy when putAttachment hashes/writes it.

Direction

Enforce the limit while reading: consume the body stream, count bytes, abort with 413 the moment the counter passes maxAttachmentBytes (Hono's bodyLimit middleware does exactly this and takes a per-route maxSize — the simplest fix is applying it to POST /attachments with config.maxAttachmentBytes and dropping the hand-rolled checks). Keep the Content-Length pre-check as a cheap fast-fail for honest clients.

Noted but out of scope: the storage API itself is bytes-in-memory (BlobAdapter.putAttachment(data: Uint8Array) — and downloads likewise materialize the full file), so true streaming to disk needs a core adapter-contract change. Worth raising with the owner if attachment sizes grow; at the current 50 MB ceiling, bounding the inbound buffer is the part that matters.

Work items

Refs #33; kin to the closed #14 (this is the gap it left).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions