feat: security hardening — URL secret redaction + bounded error-body reads#63
Merged
Conversation
…body) Spec for the deep-audit security cluster: URL secret redaction (known sensitive query keys) across logs/telemetry/errors, an opt-in Content-Length-gated bound on the stream() error-body pre-read with a new ResponseTooLargeError, and trust_env docs. Non-streaming hard body cap deferred. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7-task TDD plan: redact_url sanitizer, StatusError + middleware rewiring, ResponseTooLargeError, opt-in max_error_body_bytes with bounded stream() pre-read, docs, and full verification. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… keys) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes two Minor leak paths from code review: redact_url now masks sensitive keys in the fragment as well as the query, and matches keys after stripping surrounding whitespace. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…re-read Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The new bounded-error-body tests raise on stream enter, so the `async with` body never runs. Match the file's existing idiom (pytest.fail(...) # pragma: no cover) to restore 100% line coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pytest.fail(...) marks a branch that must never execute, so exclude it globally in coverage.report.exclude_also instead of per-line pragmas. Drops the # pragma: no cover comments added for the bounded-stream tests, and strengthens the whitespace-padded-key redaction test to assert the masked form. Pre-existing pragmas elsewhere are now redundant and can be swept separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move URL redaction from per-emit-site _observed_url() into _emit_event itself, so the single emission boundary sanitizes the `url` attribute for both the log record and the OTel span event. A future emit site can no longer reintroduce the leak by passing a raw url, and the four middleware modules revert to their original `str(request.url)` (net-unchanged vs main). Adds direct _emit_event redaction tests for both channels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lesnik512
added a commit
that referenced
this pull request
Jun 14, 2026
Promote 2026-06-14.02-pydantic-import-isolation (#62) and 2026-06-14.03-security-hardening (#63) to changes/archive/ with status: shipped, pr, and outcome filled; move their Index lines from Active to Archived. The deep-audit umbrella bundle stays active (still spawning remediation). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the security cluster from the 2026-06-14 deep audit — the dimensions (security/supply-chain) the prior audit never covered. Design + plan:
planning/changes/active/2026-06-14.03-security-hardening/.1. URL secret redaction (3 leakage findings). New
_internal/redaction.py::redact_urlstripsuser:pass@userinfo and masks the values of known-sensitive query and fragment keys (case-insensitive, whitespace-tolerant). Wired into:StatusError._summary/__repr__(replaces the userinfo-only_strip_userinfo)_observed_url(request)helper (so a new emit site can't silently re-leak)Benign URLs (no sensitive key) pass through byte-identical.
2. Bounded error-body read (streaming finding). New public
ResponseTooLargeError+ opt-inmax_error_body_bytes: int | None = Noneon bothAsyncClientandClient. When set,stream()raises on a 4xx/5xx whose declaredContent-Lengthexceeds the cap before reading the body.Nonedefault = unchanged behavior. Honest residual (documented): a chunked error body with no declared length is still read, because a true mid-read cap would require httpx2's private_content(banned by repo invariant). The non-streaming hard cap is recorded inplanning/deferred.md.3. Docs.
trust_envproxy default, the bounded-body residual, and aStatusError.response.requestheader-reachability callout (Authorization/Cookieare not stripped — redact before logging).Also: a small coverage-config improvement (exclude
pytest.fail()lines globally instead of per-line pragmas).Test plan
redact_urlunit tests: userinfo (incl. IPv6/port), each sensitive key, fragment, whitespace-padded keys, repeated keys, benign byte-identical passthroughREDACTEDform — forStatusErrormessages and resilience events (sync + async)ResponseTooLargeErrorwithout reading; under-cap / no-cap still populateexc.response.content(sync + async);_parse_content_lengthis totalResponseTooLargeErrorpickle round-trip + public-API exportjust test— 602 passed, 100% coveragejust lint— ruff + ty cleanhttpx2._, no rawstr(request.url)in middleware, noprint(), nofrom __future__🤖 Generated with Claude Code