Feb 25, 2026·6 min read·36 visits
If you have more than 100 hosts in a Caddy block, the server switches to a 'fast path' that forgets how to read uppercase letters. Attackers can bypass host-based authentication by sending `Host: ADMIN.EXAMPLE.COM` instead of `admin.example.com`. Upgrade to 2.11.1 immediately.
A logic error in Caddy's HTTP host matcher allows attackers to bypass routing rules and associated security middleware (like authentication) by simply changing the capitalization of the Host header. This vulnerability specifically affects configurations with more than 100 hostnames, where an optimization path inadvertently switches from case-insensitive to case-sensitive matching.
In the world of software engineering, there is an old adage: "Premature optimization is the root of all evil." In the world of exploit development, we call it "job security." Caddy, the darling of modern web servers known for its automatic HTTPS and ease of use, recently fell victim to this exact trap. It wasn't a memory corruption bug, a buffer overflow, or some complex race condition. It was a simple attempt to make the server go vroom when handling large lists of domains.
The vulnerability, tracked as CVE-2026-27588, is a classic logic flaw hidden inside a performance optimization. Web standards (RFC 4343) dictate that domain names are case-insensitive. example.com, Example.Com, and ExAmPlE.cOm are all the same place. Caddy knows this. Caddy respects this. Usually.
However, someone decided that when a single matcher block contains more than 100 hostnames—a common scenario for SaaS providers or multi-tenant architectures—a linear scan was too slow. They implemented a binary search optimization. The problem? Computers are pedantic. While a linear scan using strings.EqualFold knows that 'A' equals 'a', a binary search relying on standard string sorting and equality checks does not. This created a split-brain scenario where the server's security posture depended entirely on how many domains you shoved into your config file.
Let's dig into the root cause. Caddy's MatchHost module is responsible for looking at an incoming HTTP request, grabbing the Host header, and deciding if it matches a rule. If you have a rule saying "Require Basic Auth for admin.corp.com," Caddy checks the header. If the header matches, the middleware chain executes, and the user is prompted for a password.
Here is where the logic splits. If you have 100 or fewer hosts defined, Caddy iterates through them one by one, performing a case-insensitive comparison. This is safe. However, if you add that 101st host, Caddy switches to an "optimized" path. This path sorts the list of hosts and uses sort.Search to find the target.
The fatal flaw was in the comparison logic used during this binary search. The code compared the incoming Host header directly against the stored configuration using Go's == operator. In Go, "example.com" == "EXAMPLE.COM" evaluates to false.
This means if an attacker sends a request to ADMIN.CORP.COM, the optimized matcher looks at its sorted list of lowercase domains, sees admin.corp.com, compares it to ADMIN.CORP.COM, determines they are different, and returns false. The matcher fails. The request is not considered a match for that block.
The vulnerability lived in modules/caddyhttp/matchers.go. It’s a perfect example of how a subtle assumption can break security boundaries. Here is the vulnerable logic from versions prior to 2.11.1:
// VULNERABLE CODE
if m.large() {
// fast path: locate exact match using binary search
pos := sort.Search(len(m), func(i int) bool {
// comparison is case-SENSITIVE
return m[i] >= reqHost
})
// equality check is case-SENSITIVE
if pos < len(m) && m[pos] == reqHost {
return true, nil
}
}The fix was embarrassingly simple: normalize everything to lowercase before comparing. The patch ensures that no matter how the user types the domain, the binary search sees a lowercase string, matching the normalized configuration.
// PATCHED CODE (v2.11.1)
if m.large() {
// Normalize the input before search
reqHostLower := strings.ToLower(reqHost)
pos := sort.Search(len(m), func(i int) bool {
return m[i] >= reqHostLower
})
if pos < len(m) && m[pos] == reqHostLower {
return true, nil
}
}This change restores the RFC-compliant behavior even when the optimization path is triggered.
So, how do we weaponize this? The impact depends entirely on what the matcher is guarding. In Caddy, matchers are often used as gates for middleware. A common pattern is to apply authentication only to specific internal subdomains.
The Setup:
Imagine a SaaS platform using Caddy with 150 customer domains configured. One of them is internal-admin.saas.com, protected by basicauth.
The Attack:
Standard Request: GET / HTTP/1.1 | Host: internal-admin.saas.com.
basicauth middleware is triggered.Bypass Request: GET / HTTP/1.1 | Host: INTERNAL-ADMIN.SAAS.COM.
false.basicauth middleware is skipped because the matcher didn't trigger.*) or if the backend simply serves the app based on the Host header regardless of Caddy's routing logic, the attacker gains access.This effectively turns a "secure" internal admin panel into a public-facing page, provided the backend application itself doesn't enforce a secondary layer of host validation (spoiler: they rarely do).
The mitigation is straightforward: Upgrade to Caddy v2.11.1. The Caddy team responded quickly once the issue was identified, patching the binary search logic to normalize the request host before lookup.
If you are stuck in a change-freeze or cannot upgrade immediately, there is a configuration workaround, though it is tedious. You need to ensure that no single host matcher block contains more than 100 entries.
For example, instead of:
@myhosts host a.com b.com ... (101 domains) ...You would split it:
@group1 host a.com ... (50 domains)
@group2 host ... (51 domains)This forces Caddy to use the unoptimized, linear scan path, which correctly uses strings.EqualFold and is not vulnerable to this casing bypass. But seriously, just upgrade the binary. It's a single file.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P| Product | Affected Versions | Fixed Version |
|---|---|---|
Caddy Caddy | < 2.11.1 | 2.11.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-178 (Improper Handling of Case Sensitivity) |
| CVSS v4.0 | 7.7 (High) |
| Attack Vector | Network (AV:N) |
| Impact | Integrity High, Authorization Bypass |
| Constraint | Requires > 100 hosts in matcher config |
| Exploit Status | Trivial (Change Header Casing) |
The software does not correctly resolve the case sensitivity of inputs, leading to inconsistent logic or access control bypasses.
CVE-2026-48861 is a client-side HTTP request-line CRLF (Carriage Return Line Feed) injection vulnerability in the popular Elixir HTTP client library, Mint. The vulnerability permits HTTP Request Splitting and HTTP Request Smuggling when an application forwards untrusted, attacker-controlled inputs to Mint's HTTP client requests as either the HTTP request method or target. By embedding CRLF characters within these parameters, an attacker can terminate the request line prematurely, inject malicious headers, or pipeline entirely independent requests. These smuggled requests are then processed by upstream or downstream proxy servers as separate HTTP queries on the same TCP connection. While Mint version 1.7.0 introduced target validation to secure the request target, the HTTP request method parameter remained completely unvalidated. This flaw allows attackers to bypass routing filters, access restricted internal APIs, or poison HTTP caches under default configurations.
An Inconsistent Interpretation of HTTP Requests (HTTP Request/Response Smuggling) vulnerability in the Elixir Mint HTTP client allows attacker-controlled HTTP/1 servers to desynchronize response framing on shared connections due to over-lenient parsing of sign-prefixed Content-Length headers.
An allocation of resources without limits or throttling vulnerability in Elixir Mint allows an attacker-controlled HTTP/2 server to exhaust memory in a Mint client. The vulnerability is exploited by sending a HEADERS frame without the END_HEADERS flag followed by an infinite stream of CONTINUATION frames. Because the client lacks limits on the incoming header-block accumulator, the client continuously consumes memory until an out-of-memory crash occurs.
CVE-2026-48596 is an Improper Neutralization of CRLF Sequences in HTTP Headers (HTTP Request/Response Splitting, CWE-113) in the Elixir Tesla HTTP client. The flaw resides in how multipart content-type parameters are joined and serialized, enabling attackers to inject arbitrary headers or split HTTP requests when applications pass untrusted inputs to the parameters of multipart uploads.
An improper handling of highly compressed data (decompression bomb) vulnerability exists in the Elixir Tesla HTTP client when utilizing response decompression middlewares. By serving highly compressed responses or stacked content-encoding headers, a malicious server can cause arbitrary heap exhaustion, leading to a denial of service (DoS) crash in the BEAM virtual machine.
A high-severity security vulnerability in Elixir's Tesla HTTP client library (CVE-2026-48595) allows unauthenticated remote attackers to harvest sensitive credentials, including Authorization headers and cookies. The flaw resides in the 'Tesla.Middleware.FollowRedirects' component, which performs case-sensitive lookups when stripping credentials during cross-origin redirects. Because HTTP headers are case-insensitive by RFC specifications, standard canonical casing (e.g., 'Authorization') bypasses the lowercase-only blocklist, leaking tokens to untrusted external redirect destinations.