Skip to content

fix: keep multi-step auth responses under the response body cap - #153

Merged
lesnik512 merged 2 commits into
mainfrom
fix/cap-auth-flow-bodies
Oct 3, 2026
Merged

lesnik512 merged 2 commits into
mainfrom
fix/cap-auth-flow-bodies

Conversation

@lesnik512

Copy link
Copy Markdown
Member

httpx2's _send_handling_auth reads every intermediate auth-flow response with an unbounded response.read(). Any auth= that takes more than one step therefore bypassed max_response_body_bytes, with or without redirects: the 401 challenge of httpx2.DigestAuth, and custom flows such as a token refresh that retries after calling a token endpoint. auth= has been forwarded since 0.18, so a server could make a capped client buffer an arbitrarily large challenge body.

How

With a cap set, httpware now drives the auth flow itself (auth.async_auth_flow / sync_auth_flow, public API), wrapped around the redirect loop from #152, the same nesting httpx2 uses.

  • Every wire request goes out with a no-op httpx2.Auth(), so httpx2 never applies auth on its own. Which auth runs is decided like httpx2 does: client.auth, else Basic from URL credentials, else none (_request_auth, a public-API copy of the private _build_request_auth).
  • Intermediate auth responses are closed unread and kept in history. An auth with requires_response_body = True gets each response buffered under the cap instead, so an oversized token response raises ResponseTooLargeError.
  • Behaviour otherwise matches the no-cap path, checked against native httpx2: auth steps count toward max_redirects (checked before every send), nested .history of each hop, history order across auth steps, and the error raised for a malformed Digest challenge.
  • Without a cap nothing changes.

Side effects

  • DigestAuth now answers a challenge that arrives after a redirect under a cap. In 0.20.0 it could not (noted in that release).
  • Credentials embedded in a redirect Location are no longer applied on the capped path, matching httpx2 (0.20.0 sent hops with auth=None, which applied them).
  • stream() with a requires_response_body auth yields a body already read under the cap. httpx2 reads that body anyway; docs/errors.md now says so.
  • _read_capped_and_close[_async] is shared by _terminal and the auth loop.

Tests

tests/test_client_body_cap_auth.py, sync and async mirrored: Digest challenge body read only without a cap (verbs and stream()), token refresh under and over the cap (verbs and stream()), URL credentials with and without a cap, Digest after a redirect with identical nested history with and without a cap, auth steps counting toward max_redirects, and a malformed challenge raising the same TransportError. The Digest and stream tests fail against main.

Verification

just lint-ci, just test-ci (1001 passed, 100% coverage), just docs-build, just adr-check.

httpx2 reads every intermediate auth-flow response (a DigestAuth
challenge, a token-refresh call) without a limit. With a cap set,
httpware now drives the auth flow itself around its redirect loop.
Also cover stream() with auth flows and share the capped read-and-close
step between the terminal and the auth loop.
@lesnik512
lesnik512 merged commit afeaf59 into main Oct 3, 2026
13 checks passed
@lesnik512
lesnik512 deleted the fix/cap-auth-flow-bodies branch October 3, 2026 09:50
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