CVE Patch Review

GHSA-Q6GH-6V2R-HJV3: Root-Cause Fix for Cross-Origin Redirect Header Leakage

GHSA-Q6GH-6V2R-HJV3 · Updated 2026-07-09 Root-cause

Summary

Micronaut's HTTP client previously reused most request headers across redirects, with only a small static blocklist for transport and body-related headers. That behavior allowed Authorization, Proxy-Authorization, and Cookie headers to flow to a different origin after a redirect. The patch changes redirect header propagation from a single static blocklist to origin- and method-aware filtering, adds configuration for filtered header sets, and introduces tests covering direct, same-origin, and cross-origin redirect behavior. Based on the supplied diffs, this addresses the root cause rather than merely suppressing one manifestation.

Analysis

Vulnerability

The vulnerable redirect logic in Micronaut HTTP client copied nearly all headers from the original request into the redirected request, excluding only a narrow static blocklist focused on transport and body semantics. In the vulnerable DefaultHttpClient implementation, the blocklist contained Host, Content-Type, Content-Length, Transfer-Encoding, and Connection, but not credential-bearing headers such as Authorization, Proxy-Authorization, or Cookie. As a result, a redirect to a different origin could exfiltrate session cookies or bearer credentials to an attacker-controlled endpoint. This matches the advisory description for GHSA-Q6GH-6V2R-HJV3.

private static final HttpHeaders REDIRECT_HEADER_BLOCKLIST;

static {
    REDIRECT_HEADER_BLOCKLIST = new DefaultHttpHeaders();
    REDIRECT_HEADER_BLOCKLIST.add(HttpHeaderNames.HOST, "");
    REDIRECT_HEADER_BLOCKLIST.add(HttpHeaderNames.CONTENT_TYPE, "");
    REDIRECT_HEADER_BLOCKLIST.add(HttpHeaderNames.CONTENT_LENGTH, "");
    REDIRECT_HEADER_BLOCKLIST.add(HttpHeaderNames.TRANSFER_ENCODING, "");
    REDIRECT_HEADER_BLOCKLIST.add(HttpHeaderNames.CONNECTION, "");
}

if (!REDIRECT_HEADER_BLOCKLIST.contains(originalHeader.getKey())) {
    redirectRequest.header(originalHeader.getKey(), value);
}

The issue is not just an incomplete denylist entry. The deeper flaw is that redirect header copying was not conditioned on origin boundaries. Sensitive headers were treated the same for same-origin and cross-origin redirects, which is unsafe for authentication material. The vulnerable behavior is shown in the supplied patch context from commit 64e539736b8168f201d868b02ace50fe14f57418.

Patch

The patch replaces the single static redirect blocklist with configurable, context-sensitive filtering in HttpClientConfiguration and DefaultHttpClient. Three filter classes are introduced: headers always filtered on redirects, headers additionally filtered when the redirect does not preserve the body, and headers additionally filtered for cross-origin redirects. The new default cross-origin filtered set includes Authorization, Proxy-Authorization, and Cookie, as shown in commit 9770328999f490bdfbb9e25addd45bf73d4a173a, commit 70cab4b44fbf985faba2846091f2356b5bd70719, and commit 64e539736b8168f201d868b02ace50fe14f57418.

private Set<String> redirectCrossOriginFilteredHeaders = new LinkedHashSet<>(CollectionUtils.setOf(
    HttpHeaders.AUTHORIZATION,
    HttpHeaders.PROXY_AUTHORIZATION,
    HttpHeaders.COOKIE
));

private DefaultHttpHeaders buildRedirectFilteredHeaders(boolean crossOrigin, boolean preserveBody) {
    DefaultHttpHeaders headers = new DefaultHttpHeaders();
    for (String header : configuration.getRedirectAlwaysFilteredHeaders()) {
        headers.add(header, "");
    }
    if (crossOrigin) {
        for (String header : configuration.getRedirectCrossOriginFilteredHeaders()) {
            headers.add(header, "");
        }
    }
    if (!preserveBody) {
        for (String header : configuration.getRedirectAdditionalNonPreserveBodyFilteredHeaders()) {
            headers.add(header, "");
        }
    }
    return headers;
}

The redirect copy path now computes whether the redirect target is same-origin using isSameOrigin(request.getUri(), redirectRequest.getUri()) and selects one of four memoized filter sets: same-origin/cross-origin × preserve-body/non-preserve-body. This is a structural change in the decision model, not just an added header name. The tests added in 9770328999f490bdfbb9e25addd45bf73d4a173a and 64e539736b8168f201d868b02ace50fe14f57418 verify that direct requests preserve headers, same-origin redirects retain credential headers, and cross-origin redirects strip them while preserving unrelated headers.

Review

Pros

  • The patch addresses the actual trust-boundary error: redirect header propagation now depends on whether the destination is same-origin or cross-origin.
  • Credential-bearing headers are explicitly filtered by default for cross-origin redirects: Authorization, Proxy-Authorization, and Cookie.
  • The implementation separates always-filtered, body-related, and cross-origin-only filters, which makes the behavior easier to reason about and safer to extend.
  • Tests cover direct requests, same-origin redirects, and cross-origin redirects, reducing regression risk and demonstrating intended semantics.
  • The use of memoized header filter sets avoids rebuilding blocklists on every redirect path.
  • Configuration accessors in HttpClientConfiguration make the policy visible and tunable for downstream users.

Cons

  • The patch remains denylist-based. Only the explicitly listed cross-origin sensitive headers are stripped by default. Other custom or less common credential headers could still be forwarded unless users extend the configuration.
  • The supplied snippets show filtering for Cookie but do not show analogous treatment for other state-bearing headers such as application-specific session headers; safety depends on operator awareness and configuration.
  • isSameOrigin falls back to false on exception, which is fail-safe for confidentiality, but the exact origin comparison semantics are delegated to RequestKey; reviewers should confirm that scheme, host, and effective port normalization match expected origin rules in all transports.
  • The copy constructor assignments shown for the configuration sets appear to reuse set references rather than deep-copying them. That is not the vulnerability at issue, but it is a minor mutability concern worth reviewing separately.

Verdict

Root-cause.

This patch fixes the core design flaw by making redirect header forwarding origin-aware and by introducing a dedicated cross-origin credential filter set, rather than merely appending one more header to the old static blocklist. The added tests directly validate the security boundary that was previously missing. While the model still relies on configurable denylisted header names and therefore may not catch every custom secret-bearing header by default, the supplied changes materially correct the vulnerable behavior described in the advisory and in the implementation changes from 64e539736b8168f201d868b02ace50fe14f57418.

Sources