OAuth introspection endpoints with token-in-URL-path require breaking httpx exception chains, not just sanitizing message strings
Some OAuth providers (e.g. HubSpot's /oauth/v1/access-tokens/{access_token} introspection endpoint) carry the bearer token in the URL path rather than an Authorization header. When the request fails, both httpx.HTTPStatusError (constructed by raise_for_status() with a message that embeds the request URL) and httpx.RequestError subclasses (whose str() commonly includes the URL the transport was attempting) carry the secret in their textual form. Sanitizing only the wrapping exception's own message, e.g. raise IdentityFetchError(f"...{exc.response.status_code}") from exc, leaves the URL/token reachable through __cause__, which Python's default traceback renderer, traceback.format_exception(), and logging.exception() all walk and print. Verified 2026-05 against httpx 0.27+ behavior. Regression tests that only check str(wrapping_exc) will pass while real production logs still leak the token.
When wrapping httpx exceptions raised from a request whose URL contains a secret, use raise WrappedError(safe_message) from None (not from exc) inside the except handler. from None sets both __cause__ = None and __suppress_context__ = True, which together prevent traceback renderers from walking back to the leaky original. Build the safe_message from non-leaky primitives only (e.g. exc.response.status_code int, type(exc).__name__, static strings), never from str(exc). Add a regression test that asserts the secret does not appear in "".join(traceback.format_exception(exc)) for both the HTTPStatusError and RequestError paths, plus checks that exc.__cause__ is None and exc.__suppress_context__ is True.