sxh 3bb9a1c6ba
fix(logging): redact credential-bearing header values in urllib3's header-parse dump (#5752)
* fix(logging): redact credential-bearing header values in urllib3's header-parse dump

* fix(logging): bind redacted before the header-dump pass runs

* fix(logging): collapse the first field of a header dump, which shares its line with the url argument

* fix(logging): collapse every header-dump field, in the message and in the exc_info traceback

* test(logging): give the traceback message copy real CRLF

The message half of the record built its payload with literal backslash-r
text while the exc_info copy used real CRLF, so the two halves rendered the
dump in different shapes and the message only exercised the pass through a
fragment match instead of the escape the real record produces.

* fix(logging): redact obs-fold continuations and Proxy-Authentication-Info

A malformed response can fold a header value onto the next line, and this
warning exists precisely to dump malformed upstream bytes, so a folded
credential cannot be assumed absent: the pass replaced only the matched
segment and left the continuation - `CookieSecret; Path=/` - in the log
whole. A collapsed field now also swallows the SP/HTAB-prefixed segments
that follow it, while a non-sensitive field keeps its own continuation.

Proxy-Authentication-Info is the proxy-side twin of Authentication-Info,
which was already on the list; nextnonce reaches the payload and was
logged verbatim in both the message and the exception copy.

* fix(logging): redact a credential folded under an unlisted field, past repr escapes, and behind an empty first segment

Addresses the three P1 findings on the header-parse dump pass:

- A credential that exists only in an obs-fold continuation is now collapsed.
  ``_CREDENTIAL_FIELD_RE`` no longer carries the ``^``: the anchor is applied
  past a segment's fold marker instead, because under RFC 7230 unfolding a
  bearer token folded under a field this list does not name is still a bearer
  token.
- ``_OBS_FOLD_RE`` now also recognises the repr-escaped ``\t`` spelling. The
  payload reaches the record through ``!r``, so a real tab is the two
  characters backslash-t and never matched a literal-class marker.
- The continuation walk runs even when the matched value is empty, so
  ``Set-Cookie:\r\n Secret; Path=/`` collapses instead of leaving the secret in
  the segment that follows.

Evidence: 3 new regression tests, red on the previous head (3 failed / 23
passed) and green on this commit (26 passed) against urllib3 2.7.0; ``ruff
check`` and ``ruff format --check`` clean on both files.

* test(logging): move the header-parse tests out of the EOF hunk

Upstream `22b5b59` appended a redaction test at the end of this file, the same
position this branch appends its own block to, so the PR reported a conflict.
Relocating this branch's block to the preceding top-level boundary frees the
EOF hunk; the three-way merge against current `main` is then clean.

Content is unchanged: base->head is a single pure insertion of the same 312
lines (0 deletions, same line multiset as before this commit), and the merge
result is byte-identical in aggregate to resolving the conflict by hand.

* fix(logging): treat an escaped bare CR as a header-dump line break

urllib3's header-parse warning carries the unparsed block through repr(), so
its separators are escaped text. The splitter recognised escaped CRLF and
escaped LF but not escaped bare CR, which http.client also accepts as a line
terminator: a payload separated on bare CR alone stayed in one segment, no
field name landed at a segment start, so the credential anchor never fired
and the Set-Cookie and signed Location values were logged verbatim in both
the message and the repeated exc_text. Add the escaped \r alternative and an
end-to-end case driven through the real parse_headers/assert_header_parsing
pair on the pinned urllib3 2.7.0.

Co-Authored-By: sxh313 <sxh313@users.noreply.github.com>

* test(logging): pin the bare-CR header dump on the JSON path too

The Gateway runs with logging.enhance.format=json, and JsonTraceFormatter
renders the exception itself, so the text-format case alone left the deployed
emission unguarded for an escaped bare-CR payload. Record the measured cost of
reordering the break alternation, which obs-fold continuations depend on.

---------

Co-authored-by: sxh313 <sxh313@users.noreply.github.com>
2026-09-26 15:06:48 +08:00
..