Skip to content

fix: bound buffered-op body size and fail closed on classifier over-match#102

Merged
alukach merged 2 commits into
mainfrom
fix/buffered-body-size-guard
Jul 1, 2026
Merged

fix: bound buffered-op body size and fail closed on classifier over-match#102
alukach merged 2 commits into
mainfrom
fix/buffered-body-size-guard

Conversation

@alukach

@alukach alukach commented Jun 30, 2026

Copy link
Copy Markdown
Member

Follow-ups to #100 (merged as 60b4278), from a code review of that change. Two small correctness fixes plus comment accuracy.

1. Bound the buffered-op body before the eager pre-read

#100 moved the body read for buffered ops (multipart control + batch delete) to the top of handle_request, ahead of resolve — but that's also ahead of dispatch_operation's check_upload_size. So an oversized declared Content-Length was fully materialized into memory before being rejected, and the three multipart-control ops (which never call check_upload_size) had no size bound at all. Pre-#100, the header-only guard fired before any bytes were read.

This restores that ordering: run the header-only check_upload_size in the op_needs_buffered_body branch, before collect_body.

2. Fail closed instead of panic on classifier over-match

op_needs_buffered_body must mirror the NeedsBody arms of dispatch_operation. The under-match direction already fails closed (a NeedsBody op the classifier missed returns a clean 500). The over-match direction did not: if a buffered-classified op ever dispatched to Forward, the body was already taken by the pre-read and body.take().expect("streaming op retains its body") would panic (worse on Workers than a 500). The Forward arm now returns a 500 instead, matching the other direction.

Not reachable with today's op set, but it's latent fragility on the same hand-maintained invariant.

3. Comment accuracy

  • The FixedLengthStream PUT branch streams but is not zero-copy (chunks cross the transform boundary); only the raw-stream branch is.
  • Documented that the dropped pipe_to promise isn't a silent failure — a pipe error also errors readable, so the awaited outbound fetch fails and surfaces it.

Tests

Core suite (115) passes. cf-workers is wasm-checked. (Note for maintainers: cf-workers has no executable test coverage — worker::Fetch's future is !Send on the host target so the crate doesn't compile natively, and CI only cargo checks it on wasm32. fixed_body_length would be a clean unit test if that's ever resolved.)

Deliberately out of scope (review noted, not done here)

  • Parse once / drop the classifier: split resolve_request_with_metadata into a sync parse phase + async authorize phase and derive the buffer/stream decision from the single parsed S3Operation. Removes the double-parse, the op_needs_buffered_body classifier, the drift hazard, and the need for fix Support temporary credential generation #2 entirely. Larger refactor — separate PR.
  • 411 for a plain PUT with no Content-Length (currently falls back to the chunked path that hangs).
  • Plain PutObject drops x-amz-checksum-* while plain UploadPart now preserves them — worth reconciling.

🤖 Generated with Claude Code

…atch

Review follow-ups on the cross-request body-I/O change:

- handle_request now runs the header-only check_upload_size before the eager
  pre-read of buffered ops, restoring the pre-read size rejection. Without it an
  oversized batch DeleteObjects was fully materialized before being rejected,
  and the multipart-control ops had no size bound at all.

- The Forward arm fails closed with a 500 instead of panicking via expect() if
  op_needs_buffered_body ever over-matches an op that dispatches to Forward (the
  body would already be taken by the pre-read). Mirrors the under-match path.

- Comment accuracy: the FixedLengthStream PUT branch streams but is not
  zero-copy (chunks cross the transform boundary), and the dropped pipe promise
  surfaces failures via the awaited outbound fetch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown

🚀 Latest commit deployed to https://multistore-proxy-pr-102.development-seed.workers.dev

  • Date: 2026-06-30T22:59:02Z
  • Commit: 03f5113

@github-actions github-actions Bot added the fix label Jun 30, 2026
Dedupes the error_response + maybe_inject_server_timing + return idiom that
the buffered-op size guard, the body-read failure, and the body-already-consumed
guard each repeated. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alukach alukach merged commit 06ac75b into main Jul 1, 2026
12 checks passed
@alukach alukach deleted the fix/buffered-body-size-guard branch July 1, 2026 04:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support temporary credential generation

1 participant