Skip to content

fix: include the effective host in the cache key - #9

Merged
umputun merged 1 commit into
masterfrom
fix/cache-key-host
Aug 19, 2026
Merged

umputun merged 1 commit into
masterfrom
fix/cache-key-host

Conversation

@paskal

@paskal paskal commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Both cache middlewares built the key from req.URL only. Request.Host overrides the URL host on the wire, so two requests to the same upstream URL but different virtual hosts shared one cache entry, and a response cached for one host was served for the other:

client := requester.New(http.Client{}, middleware.Cache()).Client()

req1, _ := http.NewRequest("GET", upstream, http.NoBody)
req1.Host = "victim.example"
client.Do(req1)

req2, _ := http.NewRequest("GET", upstream, http.NoBody)
req2.Host = "attacker.example"
client.Do(req2) // served from the entry cached for victim.example, headers included

The key now carries the effective host, Request.Host when set and the URL host otherwise, in both middleware.Cache and the middleware/cache adapter.

While in the key builders, every part now goes in as a length-prefixed field. Concatenating raw values let one part be read as another one, so a header value could be crafted to produce the key of a different set of headers, and with CacheWithBody/KeyWithBody a body could shift the host field. Header records are length-prefixed per name, per value and as a whole record, so a multi-valued header cannot look like several headers.

Behaviour change worth noting on release: every default key changes, KeyFunc keys do not. middleware.Cache is in-memory and only loses entries for the lifetime of the process, but a persistent or shared cache behind middleware/cache (lcw and friends) starts cold after the upgrade, and instances on mixed versions use separate keyspaces until all of them are updated. Entries cached before the upgrade are unreachable rather than served, which is the point of the change.

README documents the host as part of the key composition for both middlewares.

Both cache middlewares keyed responses on the URL only, so requests
differing solely by Request.Host shared a cache entry and a response
cached for one virtual host was served for another one.

The key now carries the effective host, Request.Host when set and the URL
host otherwise, and every part goes in as a length-prefixed field so a
value of one part can't be read as another one.
@paskal
paskal requested a review from umputun as a code owner August 19, 2026 01:19
@umputun
umputun merged commit d433a1f into master Aug 19, 2026
7 checks passed
@umputun
umputun deleted the fix/cache-key-host branch August 19, 2026 01:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants