Skip to content

Enforce attachment size limit on the byte stream#6

Open
imkp1 wants to merge 1 commit into
srkprasad1995:mainfrom
imkp1:fix/enforce-attachment-size-limit
Open

Enforce attachment size limit on the byte stream#6
imkp1 wants to merge 1 commit into
srkprasad1995:mainfrom
imkp1:fix/enforce-attachment-size-limit

Conversation

@imkp1

@imkp1 imkp1 commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

What

Enforces the 50 MB attachment cap on the actual byte stream, not just the
client-declared size.

Why

Two gaps let an upload exceed the cap:

  1. Handlerr.ParseMultipartForm(maxUploadBytes) treats its argument as an
    in-memory threshold, not a hard request limit; a larger multipart body
    spills to temp files on disk during parsing, before any size check runs.
  2. Service — the cap was only checked against header.Size
    (in.Size > maxAttachmentSize), and the write itself used an unbounded
    io.Copy. A size-spoofed or chunked upload (e.g. Size: 0) was written to
    disk in full, and only the post-hoc written != in.Size check removed it.

Net effect: a client could cause large temp/attachment files to hit disk
regardless of the 50 MB limit.

Changes

  • attachment_handler.go — wrap the request body with
    http.MaxBytesReader so an oversized upload is refused up front with 413
    rather than spilled to disk while parsing. The request cap
    (maxRequestBytes) sits a little above the file cap, leaving headroom for
    multipart framing + the small form fields, so a legitimate max-size file
    still fits.
  • attachment_service.go — copy through
    io.LimitReader(in.Reader, maxSize+1) and reject when the stream exceeds the
    cap, so enforcement no longer trusts the declared size. The cap is a struct
    field defaulted to the existing maxAttachmentSize const, purely so the
    guard is testable without streaming 50 MB.

Tests

  • TestStore_RejectsOversizeStream — a Size: 0 upload that streams more bytes
    than the (shrunk) cap is rejected, and no partial file is left on disk.
  • TestUpload_RejectsOversizeBody — a request body over maxBody returns
    413 and the service is never called.

Existing attachment tests (jail/sanitize/happy-path) still pass.

Verification

$ go test ./...   # backend — all packages ok
$ go vet ./...    # clean
$ gofmt -l …      # clean

The 50 MB attachment cap was only checked against the client-declared
size (header.Size), while the actual write used an unbounded io.Copy and
ParseMultipartForm's argument is an in-memory threshold, not a hard
limit. A size-spoofed or chunked upload could therefore spill large temp
files to disk during parsing and be written unbounded before the
post-hoc size-mismatch check removed it.

- handler: wrap the request body with http.MaxBytesReader so an
  oversized upload is refused up front (413) rather than spilled to disk
  while parsing the multipart form. The cap has headroom above the file
  size so a legitimate max-size file still fits.
- service: copy through io.LimitReader(maxSize+1) and reject when the
  stream exceeds the cap, so enforcement no longer trusts the declared
  size. The cap is a struct field (defaulted to the const) to keep the
  guard testable without streaming 50 MB.

Adds tests for both layers.
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