Uh oh!
There was an error while loading. Please reload this page.
Redact sensitive headers from debug logs - #2279
Conversation
Request and response debug logging rendered complete Netty messages, which exposed Authorization values and other credentials. Format logged HTTP messages with shared redaction for authentication and cookie headers. Reuse the same public predicate in DefaultRequest, and avoid rendering complete requests from retry and replay paths. Add regression coverage for HTTP message formatting and request string representations. FixesAsyncHttpClient#1739FixesAsyncHttpClient#1740 Codex on behalf of Pavel Ptashyts Co-Authored-By: Codex <codex@openai.com>
hyperxpro
commented
Jul 20, 2026
I would like to have a system property / environment variable to enable non-redacted logs. It is helpful in debugging at CI pipeline. Probably like AHC_ENABLE_SENSITIVE_LOGGING=true? Of course feel free to choose a better key. |
Redaction is secure by default, but it can prevent diagnosis of authentication failures that only occur in CI. Read a JVM property or environment variable once when the HTTP message formatter is initialized. Fold that immutable setting into isSensitiveHeader so both Netty logging and DefaultRequest use the same decision without repeated configuration lookups. Refs AsyncHttpClient#1739 Refs AsyncHttpClient#1740 Codex on behalf of Pavel Ptashyts Co-Authored-By: Codex <codex@openai.com>
pavel-ptashyts
commented
Jul 20, 2026
@hyperxpro Implemented in Sensitive values remain redacted by default. Unredacted logging can be enabled before
The value is calculated once and remains immutable for the process. The JVM property takes precedence over the environment variable, including allowing an explicit Thanks for the suggestion. |
| private static final String ENABLE_SENSITIVE_LOGGING_PROPERTY = "org.asynchttpclient.enableSensitiveLogging"; | ||
| private static final String ENABLE_SENSITIVE_LOGGING_ENVIRONMENT_VARIABLE = "AHC_ENABLE_SENSITIVE_LOGGING"; | ||
| private static final boolean SENSITIVE_LOGGING_ENABLED = isSensitiveLoggingEnabled(); |
There was a problem hiding this comment.
This is read once when the class loads and never rechecked. Surefire in this repo reuses a single JVM across the whole client module by default, so whichever test touches this class first locks the value for the rest of the run. That means the EnabledIfSystemProperty and EnabledIfEnvironmentVariable tests further down just get skipped in a normal build instead of actually running, so the enabled path has no real coverage right now. Might be worth resolving this per call or behind a seam that a test can override.
There was a problem hiding this comment.
Kept the startup value immutable, but replaced the conditional tests with isolated JVM probes. The system-property, environment-variable, and precedence paths now run in every normal test build.
| private static StringBuilder appendHeaders(StringBuilder value, HttpHeaders headers) { | ||
| for (Map.Entry<String, String> header : headers) { | ||
| value.append('\n').append(header.getKey()).append(": ") | ||
| .append(isSensitiveHeader(header.getKey()) ? REDACTED : header.getValue()); |
There was a problem hiding this comment.
This uses a colon followed by a space between header name and value, but DefaultRequest toString uses a colon with no space for the same kind of dump. Small thing, but the two renderings will drift if they stay separate.
There was a problem hiding this comment.
Aligned DefaultRequest with the formatter colon-space spelling. Both paths now use the same neutral redaction policy.
| * @param name the header name | ||
| * @return {@code true} for authentication and cookie headers when sensitive logging is disabled | ||
| */ | ||
| public static boolean isSensitiveHeader(CharSequence name) { |
There was a problem hiding this comment.
DefaultRequest in the base package now depends on this method and REDACTED. Since the base Request model has not reached into netty handler before, it might be cleaner to move this predicate and the redacted constant into a neutral utility package and have this class consume it instead.
There was a problem hiding this comment.
Moved the shared REDACTED value and predicate to SensitiveLoggingUtils in the neutral util package. HttpMessageFormatter retains its public facade and delegates to that policy.
| return value; | ||
| } | ||
| private static boolean isSensitiveLoggingEnabled() { |
There was a problem hiding this comment.
This reads from an environment variable that can be inherited from a container or orchestration setup. Worth considering a one time warn log when this ends up enabled, so it shows up in the logs instead of only being discoverable after something has already leaked.
There was a problem hiding this comment.
Added a one-time WARN during policy initialization whenever sensitive logging is enabled, regardless of whether it came from the system property or inherited environment.
| import java.util.List; | ||
| import java.util.Map; | ||
| import static org.asynchttpclient.netty.handler.HttpMessageFormatter.REDACTED; |
There was a problem hiding this comment.
This pulls the base Request model into a dependency on the netty handler package, which has been treated as transport level internal up to now. See the note on HttpMessageFormatter about moving the shared predicate somewhere neutral instead.
There was a problem hiding this comment.
Removed the dependency from DefaultRequest to the Netty handler package. It now consumes SensitiveLoggingUtils from the neutral util package.
| sb.append(header.getKey()); | ||
| sb.append(':'); | ||
| sb.append(header.getValue()); | ||
| sb.append(isSensitiveHeader(header.getKey()) ? REDACTED : header.getValue()); |
There was a problem hiding this comment.
Headers are redacted here but formParams a bit further down in this same method still get appended in full. If a password ever comes through as a form field it would still show up in this output untouched, worth checking if that is in scope for the issues this closes.
There was a problem hiding this comment.
| future.touch(); | ||
| LOGGER.debug("\n\nReplaying Request {}\n for Future {}\n", newRequest, future); | ||
| LOGGER.debug("\n\nReplaying request '{}' to '{}'\n for Future {}\n", newRequest.getMethod(), newRequest.getUri(), future); |
There was a problem hiding this comment.
Checked this one, NettyResponseFuture toString does not render the underlying NettyRequest headers since NettyRequest has no toString override, so this falls back to identity hash and does not leak anything today.
Might be worth a short comment here noting that this is load bearing, so a future toString added to NettyRequest does not quietly reopen this.
There was a problem hiding this comment.
Added the load-bearing comment directly in NettyResponseFuture.toString, where NettyRequest is rendered, explaining that NettyRequest must keep identity-only rendering so request headers are not exposed.
| } | ||
| @Test | ||
| @EnabledIfSystemProperty(named = "org.asynchttpclient.enableSensitiveLogging", matches = "(?i)true") |
There was a problem hiding this comment.
Same concern as on HttpMessageFormatter, this test and the one below it get silently skipped in a normal run rather than exercised, so the enabled path is effectively untested in CI as configured.
There was a problem hiding this comment.
Replaced the skipped conditional tests with isolated JVM probes. Both enabled modes and property-over-environment precedence now execute in the regular CI test suite.
hyperxpro
commented
Jul 23, 2026
Also please don't rebase your PRs as it spams my inbox :) |
Move the shared redaction policy into a neutral utility and warn once when sensitive logging is enabled. Exercise each startup configuration in an isolated JVM, align request header formatting, and document why response futures do not render request headers. ClosesAsyncHttpClient#1739ClosesAsyncHttpClient#1740 Codex on behalf of Pavel Ptashyts Co-Authored-By: Codex <codex@openai.com>
pavel-ptashyts
commented
Jul 25, 2026
Addressed the review feedback in 3b572a6:
Full JDK 11 clean verify passes, including Javadocs and Revapi. No rebase was used for this update, and I will keep future updates as regular commits. |
hyperxpro
commented
Jul 25, 2026
There are test failures, can you have a look? |
Redirect sensitive logging probe output to temporary files so Windows pipe buffers cannot block child JVMs before the parent reads output. Codex on behalf of Pavel Ptashyts Co-Authored-By: Codex <codex@openai.com>
pavel-ptashyts
commented
Jul 25, 2026
Fixed the Windows CI timeouts in 12474ee. The isolated probe JVMs wrote Logback and Netty startup output to a pipe while the parent process waited for them to exit. The smaller Windows pipe buffer filled first, which blocked each child until the 30-second timeout. Probe output is now redirected to a JUnit temporary file and read after the child exits. Validation:
No rebase was used. |
pavel-ptashyts
commented
Jul 25, 2026
The follow-up CI run is fully green: compile-and-check plus Ubuntu, macOS, and Windows on JDK 11, 17, 21, and 25 all pass. |
Uh oh!
There was an error while loading. Please reload this page.
Motivation: Non-blocking follow-ups from the PR #2279 review: dead public API surface, an undocumented invariant, and an undocumented scope gap. Modification: Removed unused HttpMessageFormatter.REDACTED/isSensitiveHeader wrappers, added an invariant comment to NettyRequest, and documented the redaction scope in SensitiveLoggingUtils javadoc. Result: No functional change
Summary
Root cause
Debug logging passed complete Netty request and response objects to SLF4J. Their
string representations included every header value, so user-supplied
Authorizationheaders and other credentials could be written to applicationlogs.
Sensitive logging
Sensitive values remain redacted by default. Full header values can be enabled
before the sensitive logging policy is initialized with either:
-Dorg.asynchttpclient.enableSensitiveLogging=trueAHC_ENABLE_SENSITIVE_LOGGING=trueThe JVM property takes precedence over the environment variable. The setting is
read once and remains immutable for the process. A warning is logged once when
sensitive logging is enabled because credentials and session data may be exposed.
Scope
This change addresses the header disclosure reported in #1739 and #1740.
Request form, query, multipart, and arbitrary body values do not have a reliable
generic sensitivity classification and remain outside this change.
Validation
HttpMessageFormatterTestandRequestBuilderTest: 33 tests passed, 0 skipped./mvnw clean verify: passed, including Javadocs and RevapiCloses#1739
Closes#1740
Codex on behalf of Pavel Ptashyts