GHSA-387M-935M-C4VW: Micronaut HTTP Client Redirect Loop DoS Gets a Root-Cause Fix
Summary
The patch introduces a configurable redirect ceiling in Micronaut's HTTP client, defaulting to 5, and enforces it in the Netty client redirect path. This directly addresses the denial-of-service condition caused by unbounded redirect following. The accompanying tests validate both configuration binding and loop termination behavior, making the change a root-cause remediation rather than a superficial guard.
Analysis
Vulnerability
GHSA-387M-935M-C4VW describes a denial-of-service condition in Micronaut's Netty HTTP client caused by unbounded redirect following. A malicious or misconfigured upstream can emit a redirect loop and keep the client recursively issuing follow-up requests without a terminating condition, leading to thread occupation and resource starvation. The advisory and patch references show that the vulnerable behavior was the absence of a maximum redirect depth in client configuration and redirect execution logic.
Source references: GitHub Security Advisory, commit f1dffff, commit 6e88a97, CVE Reports summary.
public static final int DEFAULT_MAX_REDIRECTS = 5;
private int maxRedirects = DEFAULT_MAX_REDIRECTS;
public int getMaxRedirects() {
return maxRedirects;
}
public void setMaxRedirects(int maxRedirects) {
this.maxRedirects = maxRedirects;
}The added configuration API is significant because it establishes an explicit policy boundary that was previously missing. Without such a boundary, redirect handling can be attacker-controlled for as long as the remote endpoint keeps returning redirect responses.
Patch
The patch adds a new client configuration property, maxRedirects, with a default of 5 in HttpClientConfiguration. It then enforces this limit in the Netty redirect path by tracking a per-request redirect count via a request attribute and aborting once the count exceeds the configured maximum. The implementation returns an HttpClientException with the observed redirect count when the ceiling is crossed.
private static final String REDIRECT_COUNT = "micronaut.http.client.redirect-count";
int redirectCount = request.getAttribute(REDIRECT_COUNT, Integer.class).orElse(0) + 1;
if (redirectCount > configuration.getMaxRedirects()) {
return ExecutionFlow.error(decorate(new HttpClientException("Maximum number of redirects exceeded at redirect count: " + redirectCount)));
}
redirectRequest.setAttribute(REDIRECT_COUNT, redirectCount);The test additions are aligned with the fix:
- Configuration binding coverage verifies
micronaut.http.client.max-redirectsmaps tomaxRedirects. RedirectLoopSpecvalidates that a loop is terminated once the configured depth is exceeded.- The TCK redirect test confirms that when redirect following is disabled, the client returns the redirect response rather than entering follow-up behavior.
Relevant patch references: commit f1dffff and commit 6e88a97.
Review
Pros
- The fix addresses the actual failure mode: redirect processing now has a hard upper bound, eliminating infinite redirect traversal as an unbounded control-flow condition.
- The default of
5is conservative and operationally reasonable for most HTTP client workloads. - The redirect count is tracked internally as a request attribute rather than trusting server-provided headers, which avoids attacker control over the enforcement state.
- The error path is explicit and deterministic, improving diagnosability during incident response and application debugging.
- Tests cover both behavior and configuration plumbing, reducing regression risk.
Cons
- The setter shown in the patch does not validate negative or zero values. Depending on broader framework semantics, a negative value could produce surprising behavior by failing on the first redirect rather than being rejected at configuration time.
- The review material shows enforcement in the Netty client path only. If other HTTP client implementations or redirect-capable code paths exist outside this path, they would need equivalent enforcement to guarantee uniform protection.
- The exception message exposes the redirect count but does not include target URI context, which may limit troubleshooting value in complex redirect chains.
Verdict
Root-cause.
This patch is a root-cause remediation because it introduces the missing invariant that redirect following must be bounded. The vulnerable behavior was not merely a bad edge case in one test scenario; it was the absence of a redirect depth limit in configuration and execution. The patch closes that gap by adding a default maximum, enforcing it at redirect time, and validating the behavior with dedicated tests. Based on the supplied sources, this is not a bandaid and not a partial mitigation.
Primary evidence: GitHub Security Advisory, commit f1dffff, commit 6e88a97.
Recommended Labs
Try this vulnerability pattern yourself with hands-on labs.
- DoS.java
Best fit for this Micronaut/Java advisory because it focuses on denial-of-service defense in a Java web stack. The GHSA centers on unbounded redirects causing resource starvation, and this lab should help build defensive instincts around bounding attacker-controlled request behavior and preventing CWE-400 style exhaustion.
- DoS.api
Useful as a protocol-level companion lab. Even though the advisory is in an HTTP client, the underlying lesson is controlling untrusted request/response flows to avoid runaway resource consumption. This lab reinforces defensive handling of abuse patterns that can lead to denial of service.
- Panic DoS.go
A good broader DoS hardening lab for understanding how malformed or adversarial interactions can crash or stall services. While not Java-specific, it strengthens defensive thinking about fail-safe request handling, bounded processing, and resilience against starvation conditions similar to infinite redirect loops.