From 7c5cce4b3cfc1dbce2e33fe524f982cb80f48370 Mon Sep 17 00:00:00 2001 From: elky Date: Fri, 4 Sep 2026 19:10:03 +0800 Subject: [PATCH] fix(logging): redact oauth retry transport errors --- .../src/execution_runtime/oauth_retry.rs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/aether-gateway/src/execution_runtime/oauth_retry.rs b/apps/aether-gateway/src/execution_runtime/oauth_retry.rs index 293193ebd..86c1c4533 100644 --- a/apps/aether-gateway/src/execution_runtime/oauth_retry.rs +++ b/apps/aether-gateway/src/execution_runtime/oauth_retry.rs @@ -190,7 +190,12 @@ pub(crate) async fn refresh_oauth_plan_auth_for_retry( endpoint_id = %plan.endpoint_id, key_id = %plan.key_id, status_code, - error = %err, + // `LocalOAuthRefreshError` carries provider-generated details + // in a few variants. Its `Debug` implementation deliberately + // redacts those details; using `%` here would invoke + // `Display` and could expose a raw transport URL/query or an + // upstream error body in the ops log. + error = ?err, "gateway oauth retry refresh failed" ); false @@ -336,6 +341,23 @@ mod tests { assert!(!status_may_be_oauth_invalid(429, Some("token bucket"))); } + #[test] + fn oauth_retry_failure_log_uses_redacted_error_debug() { + let error = crate::provider_transport::LocalOAuthRefreshError::TransportMessage { + provider_type: "kiro", + message: "request failed for https://user:password@example.test/token?refresh_token=oauth-retry-canary" + .to_string(), + }; + + // The retry path logs this error with `?error` (Debug), whose contract + // is to omit dynamic transport details. Keep the assertion here so a + // future change back to `%error` cannot silently reintroduce leakage. + let debug = format!("{error:?}"); + assert!(!debug.contains("oauth-retry-canary")); + assert!(!debug.contains("password")); + assert!(debug.contains("TransportMessage")); + } + #[test] fn separates_retry_candidate_from_access_token_invalid_proof() { assert!(status_proves_access_token_invalid(401, None));