curl · Report #3972385
CVE-2026-82209: domain-scoped PSL domain cookie
How it was found
Summary
When libpsl is enabled and a response host is itself a public suffix (e.g. github.io), a Set-Cookie header whose Domain attribute exactly equals that host is accepted by psl_is_cookie_domain_acceptable() but stored with tailmatch = TRUE. RFC 6265 §5.3 requires this exact-match case to produce a host-only cookie. Because curl keeps it domain-scoped, the cookie is sent to any sibling host (e.g. attacker.github.io) on a subsequent request or redirect.
Affected versions
The vulnerable code path exists in at least curl 8.21.0 (latest GA release) and the current 8.22.0-DEV tree. The parse_domain() logic has been unchanged across many prior releases. libpsl must be enabled at build time (--with-libpsl), which most distribution packages do.
Root cause
In lib/cookie.c, parse_domain() unconditionally sets co->tailmatch = TRUE for every explicit non-IP Domain attribute (line ~512):
if(!is_ip)
co->tailmatch = TRUE;
Later, is_public_suffix() calls psl_is_cookie_domain_acceptable(psl, "github.io", "github.io"). libpsl intentionally returns "acceptable" for exact equality — the caller is expected to handle the host-only conversion. curl never does.
When Curl_cookie_getlist() builds the outgoing Cookie header, it calls cookie_tailmatch("github.io", 9, "attacker.github.io") which returns TRUE (suffix match with a preceding .), so the cookie leaks.
Reproduction
-
Build curl at commit c2676bf9e639e31e44b12a2cc82cf2046231b056 (or any version from 8.21.0 onward) on a 64-bit Linux or macOS host, ensuring libpsl is enabled (--with-libpsl or -DCURL_USE_LIBPSL=ON). Confirm the build includes libpsl by checking that curl --version output contains libpsl.
-
Start a local HTTP server on port 8080 that serves two endpoints:
/set-domain — responds with 302, sets Set-Cookie: sid=SECRET; Domain=github.io; Path=/, and redirects to http://attacker.github.io:8080/sink. /sink — responds with 200 and echoes back the received Cookie header value in the response body.
- Map the test hostnames to localhost using curl's --resolve flag:
github.io:8080:127.0.0.1 attacker.github.io:8080:127.0.0.1
- Run the vulnerable request — make a request to http://github.io:8080/set-domain with redirect following enabled (-L) and a cookie jar (-c /tmp/domain.jar):
curl -sS -L --noproxy '*' \
--resolve github.io:8080:127.0.0.1 \
--resolve attacker.github.io:8080:127.0.0.1 \
-c /tmp/domain.jar \
http://github.io:8080/set-domain
-
Observe the response body — it will contain sid=SECRET, proving the cookie was sent to attacker.github.io (the redirect target), a sibling host under the same public suffix.
-
Inspect the cookie jar — the entry for the domain cookie will show .github.io with the subdomain-match flag set to TRUE, confirming it was incorrectly stored as domain-scoped.
-
Run a negative control — repeat the same flow but have the server set the cookie without an explicit Domain attribute (i.e., Set-Cookie: sid=HOST_ONLY; Path=/). The redirect target at attacker.github.io will receive <none> — the cookie is correctly not sent. The cookie jar will show github.io with subdomain-match FALSE (host-only).
-
Run a second negative control — issue the same Domain=github.io cookie from a sibling host (tenant.github.io) instead of the apex. Curl correctly rejects it — the cookie jar will be empty and nothing is sent to the redirect target.
Suggested fix
After psl_is_cookie_domain_acceptable() accepts exact equality between the request host and a public-suffix cookie domain, clear tailmatch so the cookie becomes host-only. Non-exact public-suffix Domain attempts should continue to be rejected. Concretely, after the PSL acceptance in Curl_cookie_add(), add:
if(co->tailmatch && domain && co->domain &&
curl_strequal(co->domain, domain) &&
psl_is_public_suffix(psl, lcookie))
co->tailmatch = FALSE;
Impact
Summary:
An attacker controlling a sibling host under a public suffix (trivial on hosting platforms like GitHub Pages, Heroku, Netlify) can receive cookies that the public-suffix apex issued with an explicit Domain matching itself. The attacker cannot plant the cookie — exploitation requires the apex to issue such a cookie and the curl client to subsequently contact the attacker sibling. The PSL boundary that libpsl is intended to enforce is violated.