Skip to content

Patch blob and diff downloads buffer the whole response body with no size cap #571

Description

[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: discussion #560 register.

Kind: bug. Source: new finding (not in the October review); register C37.

Problem

ApiClient::fetch_binary, which backs fetch_blob and fetch_diff, reads a 200 response with resp.bytes().await and applies no bound (api/client.rs#L1089-L1099). The bytes are hash-checked only after they are fully in memory.

The crate already has a shared capped body reader for this. utils::http::read_capped / read_capped_typed (utils/http.rs#L22-L40) rejects an over-large Content-Length and an over-long stream. Its doc says it was hoisted "so the self-update downloader shares the exact cap semantics". It is used by:

The patch blob and diff path, which is the one every apply/get hits, bypasses it. The diff archive's decompressed size is capped later, at 64 MiB in patch/package.rs, but the download itself is not.

Reproduced twice on main @ 1169ae6 with an integration test against ApiClient (not committed). A local server answers the authenticated blob URL with a 600 MiB application/octet-stream body. fetch_blob returned Ok(Some(len = 629145600)), buffering 600 MiB, more than twice the cap the vendor path enforces on the same client.

Symptoms

None filed.

Impact

A misbehaving or hostile endpoint can exhaust memory with one response: a mis-set --api-url/SOCKET_API_URL, a proxy, or the public patch proxy serving a wrong file. The blob is rejected afterwards anyway. This is low-to-medium risk, a consistency defect with a small fix.

Proposed change

  • In fetch_binary, replace resp.bytes() with read_capped_typed(resp, MAX_PATCH_BLOB_BYTES, kind) and map CapExceeded / Truncated onto ApiError the way the vendor path does.
  • Choose one named cap. 64 MiB matches the per-file cap the patch engine applies elsewhere (MAX_FILE_BYTES, patch/package.rs).
  • No new reader. This deletes the last uncapped bytes() body read in api/.

Size and scope

api/client.rs, ~15–30 production lines plus one test. Out of scope: timeouts (#570) and retry for blob/diff (C15).

Acceptance criteria

  • Regression tests: a blob response over the cap, either by Content-Length or by stream length, returns an error without buffering past the cap. A diff response gets the same test.
  • binary_fetch_error_classification_e2e, blob_fetcher_edges_e2e and covgap_api_blob_fetcher stay green.

Dependencies

None. It is best landed with or next to #570 because both touch fetch_binary.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:claimedagent:triagedarch-auditFiled by a scheduled architecture audit routine (see the architecture review discussion)bugSomething isn't workingpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions