Skip to content

Body lifecycle: zero-length chunks in streamBody, multipart boundary quoting, withResponseLogging after close() #77

Closed

Description

@Wahbeh-Mohammad

Part of the audit remediation umbrella #67. Milestone 4. Severity: HIGH (first item), MEDIUM (rest). Requirements: BODY-10, HTTP-39, HTTP-51, BODY-26, BODY-28, IO-16.

Current SDK behavior

  • Empty chunks reach the sink. #writeExactly (packages/core/src/body/stream-body.ts:79-91)
    reads, checks done, checks over-run, then writes. There is no value.length === 0 guard.
    A source that enqueues new Uint8Array(0) is never diagnosed; the auditor's probe forwarded
    200,000 empty chunks before the source closed, then reported
    EndOfStreamError: delivered 0 of 3 bytes. Each empty chunk reaches the transport sink.
    io/buffered-sink.ts:66-71 documents that a zero-length chunk is HTTP/1.1 chunked encoding's
    terminating chunk. io/retention-window.ts:177-183 and body/response-body-logging.ts:82-88
    enforce the rule that this path omits.
  • Multipart boundary is not quoted. multipart-body.ts:160 builds
    multipart/form-data; boundary=${boundary}. BOUNDARY_PATTERN (:29-31) admits RFC 2046
    bchars, which include space, ,, :, =, ?, /, (, ). Those are not tchar.
    multipartBody(parts, 'a,b') yields a header Node's own FormData parser rejects with
    TypeError: Failed to parse body as FormData.
  • snapshot() after close() poisons error(). withResponseLogging's snapshot()
    (response-body-logging.ts:241) calls startDrain unconditionally. closeDelegate (:62)
    already released the reader, so the drain throws
    TypeError: Invalid state: The reader is not attached to a stream. The catch at :119-122
    caches it, and error() (:244) returns it forever. read() after close() rejects with the
    same raw TypeError instead of ClosedResourceError.
  • No Node-conformance coverage for the stream bridges. toReadableStream
    (io/buffered-source.ts:306), toWritableStream (io/buffered-sink.ts:127),
    io/tee-sink.ts:138, withRequestLogging, withResponseLogging have zero cases under
    tests/node-conformance/.

Expected behavior

streamBody treats a zero-length read for a positive request as a stream-contract violation
and writes no empty chunk to the sink. The multipart Content-Type a peer receives parses
with any RFC 9110 parameter parser. After close(), snapshot() returns the captured prefix
without starting a drain, read() rejects with ClosedResourceError, and error() reports
only a real drain failure. The pull, cancel, and lock behavior of the Web Streams bridges is
proven on Node as well as on Bun.

Notes and leads

  • Empty chunks: if (value.length === 0) throw new SourceContractViolationError(...), same
    wording as retention-window.ts. Test an empty-only source and an empty chunk between real
    chunks.
  • Boundary: quote when the boundary is not a pure tchar token (boundary="a,b"), reusing
    MediaType's parameter quoting. Or narrow validateBoundary to tchar and record the
    narrowing. Round-trip test through Node's Response.formData().
  • Logging tap: check state.closed at the top of startDrain, snapshot, and read.
    Tests: close-then-snapshot, close-then-read, close-then-error.
  • Node conformance: add one *.test.mjs per bridge under tests/node-conformance/. Cases
    belong there, not only in bun run test (CLAUDE.md, runtime-divergent surface).
  • Patch changeset for @dexpace/core.

Activity

  1. added
    type:bugDefect in shipped behavior
    area:coreCore HTTP, IO, body, context, encoding: HTTP-* IO-* BODY-* CTX-* UTF-*
    on Sep 4, 2026
  2. Wahbeh-Mohammad commented on Sep 5, 2026

    @Wahbeh-Mohammad
    ContributorAuthor

    Addressed in PR #91, merged into audit/remediation-67; reaches mvp through PR #98. Decision record: docs/audit-67-decisions.md.

  3. added this to the v1/MVP milestone on Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:coreCore HTTP, IO, body, context, encoding: HTTP-* IO-* BODY-* CTX-* UTF-*type:bugDefect in shipped behavior

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions