4 KiB
Caddy — CWE-407 Disclosure Brief
2026-03-27 · Patch available — awaiting upstream merge
Finding
One algorithmic complexity defect in Caddy's reverse proxy load balancer. The consistent-hashing policy recalculates xxhash for all upstream hosts on every request instead of using a pre-computed hash ring, producing O(N) hash work per request. Patched.
The Defects
caddy-0001 (PATCHED — HIGH): modules/caddyhttp/reverseproxy/
// hostByHashing() — called per request for consistent-hash policy:
func (r *reverseProxy) hostByHashing(req *http.Request, pool UpstreamPool) *Upstream {
key := r.hashKey(req)
for _, upstream := range pool {
h := xxhash.Sum64String(upstream.Dial + key)
// compare h to select upstream
}
// O(N) xxhash recalculation per request — N = number of upstream hosts
}
hostByHashing() recomputes xxhash.Sum64String(upstream.Dial + key) for every upstream on every request. For N upstream hosts and R requests per second: O(R × N) hash operations. The upstream dial addresses are static between config reloads — their base hashes can be pre-computed once and stored in a sorted ring. Fix: pre-computed hash ring — sort upstreams by xxhash(upstream.Dial) at config load, then per-request work reduces to one xxhash(key) + O(log N) binary search for ring position.
Complexity Proof
Let N = number of configured upstream hosts (backends), R = requests per second using the consistent-hash load balancing policy.
- Defective:
xxhash.Sum64String(upstream.Dial + key)called N times per request.upstream.Dialis static between reloads;keyis per-request.- Total hash operations per second: O(R × N).
- Fixed: pre-computed ring with sorted
xxhash(upstream.Dial)values.- Per-request work: one
xxhash(key)+ binary search over N pre-computed positions. - Total per second: O(R × (1 + log N)) hash + compare operations.
- Per-request work: one
At N=50 upstreams: defective=50 hash calls per request, fixed=1 hash + 6 comparisons per request. ~8× reduction in hash work per request; speedup grows linearly with N.
The pre-computed ring also enables standard consistent-hashing jump/virtual-node distributions, improving load balance uniformity beyond the current implementation.
Impact
All Caddy deployments using the consistent_hashing load balancing policy in reverse proxy configurations. Caddy is widely used as a production reverse proxy with automatic HTTPS, particularly in developer-facing and smaller-scale production environments. The hot path fires on every request routed through a consistent-hash upstream group.
The Fix
Pre-compute the hash ring at config load in Provision() or equivalent:
// Before — per request, O(N) hash work:
func (r *reverseProxy) hostByHashing(req *http.Request, pool UpstreamPool) *Upstream {
key := r.hashKey(req)
for _, upstream := range pool {
h := xxhash.Sum64String(upstream.Dial + key)
// select by h
}
}
// After
// CWE-407 fix: pre-computed hash ring for O(log N) lookup instead of O(N) per-request hashing.
// At Provision() time:
type ringEntry struct {
hash uint64
upstream *Upstream
}
// Build ring: for each upstream, compute xxhash(upstream.Dial), sort by hash.
r.ring = buildRing(pool) // O(N log N) once at config load
// Per request:
func (r *reverseProxy) hostByHashing(req *http.Request) *Upstream {
key := xxhash.Sum64String(r.hashKey(req)) // O(1)
return r.ring.lookup(key) // O(log N) binary search
}
Patch
defects/caddy/patch/caddy-0001-reverseproxy-hash-ring.patch
What We Ask
- Confirm receipt and assign a GitHub Security Advisory or issue reference.
- Validate the patch against the reverse proxy load balancing test suite.
- Assess CVE eligibility — caddy-0001 fires on every request using consistent-hash routing.
- Coordinate a disclosure date — we are targeting 90 days from first contact.
Contact: see cover email. This brief is confidential until coordinated disclosure.