CVE-2026-12304
Overview
Background
- `CookieStore`
- the DOM Cookie Store API object that lets a page asynchronously read cookies and receive change notifications for its origin.
- `CookieStoreNotifier`
- an
nsIObserverthat listens for global cookie-change notifications and dispatches matching changeEvents to a page’sCookieStore. - Base domain
- the registrable domain (e.g.
example.com) shared by every subdomain, which alone cannot distinguisha.example.comfromb.example.com. - `CookieCommons::DomainMatches`
- a networking helper that tests whether a given
Cookie’s domain scope actually applies to a specific host.
Root Cause Analysis
CookieStoreNotifier::Create built a notifier keyed only on baseDomain and principal->OriginAttributesRef(), and CookieStoreNotifier::Observe forwarded any observed cookie change whose base domain and origin attributes matched, without checking the concrete host of the subscribing principal. The invariant that a page must only observe cookie changes actually scoped to its own origin was violated, because all subdomains of a registrable domain share the same base domain, so a change to a cookie scoped to a sibling host (or a host-only cookie on another subdomain) was delivered to an unrelated origin’s CookieStore.
The fix captures the principal’s GetAsciiHost into a new mHost member at creation time and, in Observe, calls net::CookieCommons::DomainMatches(net::Cookie::Cast(cookie), mHost) to drop any notification whose cookie does not actually apply to that host. This restores same-origin scoping because DomainMatches enforces the real host/domain rules that base-domain equality could not.
DomainMatches filter so only cookies that truly apply to the subscribing host are delivered.Attack Path
- Establish sibling origins
An attacker controls or influences one subdomain (e.g.
evil.example.com) sharing a registrable base domain with a victim subdomain (app.example.com). - Subscribe to cookie changes
The attacker page opens a
CookieStoreand registers change listeners, creating aCookieStoreNotifierscoped only to the shared base domain and origin attributes. - Trigger victim cookie writes Cookie changes occur on the victim host, either via normal victim activity or attacker-induced navigation/requests.
- Receive cross-origin notifications
Because
Observematched on base domain without a host check, the attacker’sCookieStorereceives change events for cookies scoped to the victim host, leaking their names and values.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
mOriginAttributesdom/cookiestore/CookieStoreNotifier.cpp |
modified |
Files Changed
dom/cookiestore/CookieStoreNotifier.cppdom/cookiestore/CookieStoreNotifier.h
Audit Directions
- Base-domain scopingFlag any code that authorizes or routes data using
baseDomain/registrable-domain equality alone, since it cannot separate sibling subdomains and needs an additional host or full-origin check. - Observer fan-out filtersAudit
nsIObserver::Observeimplementations that dispatch security-sensitive events to per-origin objects, ensuring each notification is re-validated against the subscriber’s exact origin rather than a coarse key. - Cookie domain matchingWherever cookies are matched to a consumer, confirm
CookieCommons::DomainMatches(or equivalent host-vs-domain logic) is applied, not just origin attributes or domain prefixes.
Patch
diff --git a/dom/cookiestore/CookieStoreNotifier.cpp b/dom/cookiestore/CookieStoreNotifier.cpp
index 73f2b3e9617..1a6561fea35 100644
--- a/dom/cookiestore/CookieStoreNotifier.cpp
+++ b/dom/cookiestore/CookieStoreNotifier.cpp
@@ -47,13 +47,18 @@ already_AddRefed<CookieStoreNotifier> CookieStoreNotifier::Create(
return nullptr;
}
+ nsCString host;
+ if (NS_WARN_IF(NS_FAILED(principal->GetAsciiHost(host))) || host.IsEmpty()) {
+ return nullptr;
+ }
+
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (NS_WARN_IF(!os)) {
return nullptr;
}
RefPtr<CookieStoreNotifier> notifier = new CookieStoreNotifier(
- aCookieStore, baseDomain, principal->OriginAttributesRef());
+ aCookieStore, baseDomain, host, principal->OriginAttributesRef());
nsresult rv =
os->AddObserver(notifier,
@@ -68,9 +73,10 @@ already_AddRefed<CookieStoreNotifier> CookieStoreNotifier::Create(
CookieStoreNotifier::CookieStoreNotifier(
CookieStore* aCookieStore, const nsACString& aBaseDomain,
- const OriginAttributes& aOriginAttributes)
+ const nsACString& aHost, const OriginAttributes& aOriginAttributes)
: mCookieStore(aCookieStore),
mBaseDomain(aBaseDomain),
+ mHost(aHost),
mOriginAttributes(aOriginAttributes) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aCookieStore);
@@ -129,6 +135,10 @@ CookieStoreNotifier::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
+ if (!net::CookieCommons::DomainMatches(net::Cookie::Cast(cookie), mHost)) {
+ return NS_OK;
+ }
+
bool isHttpOnly;
rv = cookie->GetIsHttpOnly(&isHttpOnly);
if (NS_WARN_IF(NS_FAILED(rv))) {
diff --git a/dom/cookiestore/CookieStoreNotifier.h b/dom/cookiestore/CookieStoreNotifier.h
index 7eefee65b35..b064293fc3e 100644
--- a/dom/cookiestore/CookieStoreNotifier.h
+++ b/dom/cookiestore/CookieStoreNotifier.h
@@ -30,6 +30,7 @@ class CookieStoreNotifier final : public nsIObserver {
private:
CookieStoreNotifier(CookieStore* aCookieStore, const nsACString& aBaseDomain,
+ const nsACString& aHost,
const OriginAttributes& aOriginAttributes);
~CookieStoreNotifier();
@@ -39,6 +40,7 @@ class CookieStoreNotifier final : public nsIObserver {
CookieStore* mCookieStore;
nsCString mBaseDomain;
+ nsCString mHost;
OriginAttributes mOriginAttributes;
nsTArray<RefPtr<Event>> mDelayedDOMEvents;