CVE-2026-74986
Overview
Background
- `GetSubresourceCacheValidationInfo`
nsContentUtilshelper that decides whether a fetched subresource may be reused from a shared cache or must be revalidated per document.- `Vary` header
- an HTTP response header naming the request headers that a response’s content depends on, signaling that different header values yield different bodies.
- `mMustRevalidate`
- a flag in the validation-info struct that, when set, forces the subresource to be re-fetched/re-validated rather than served from a cross-document cache.
- Subresource cache
- Firefox’s per-process in-memory cache that lets multiple documents reuse an already-parsed resource (e.g. a CSS stylesheet) to save work.
Root Cause Analysis
The vulnerable path was nsContentUtils::GetSubresourceCacheValidationInfo, which computed cacheability for a subresource fetched over HTTP without inspecting the response’s Vary header. The invariant being violated is that a cached response may only be shared across documents when its body does not depend on request-specific, context-sensitive inputs such as the request’s cookies. Because Vary: Cookie (or the catch-all Vary: *) was ignored, a response whose content legitimately varies per user/session could be stored once and then served to a different document that carried different cookies, defeating the site-isolation/caching boundary.
The fix reads the vary response header and, when it is not already forced to revalidate, tokenizes the comma-separated list and sets info.mMustRevalidate = true if any token is * or (case-insensitively) cookie. This ensures cookie-dependent (or wildcard-varying) responses are revalidated instead of blindly reused, restoring the requirement that shared cache entries be context-independent.
Vary header that declares the response depends on request-specific data like cookies. The fix closes this by forcing revalidation whenever Vary contains cookie or the * wildcard.Attack Path
- Serve a cookie-varying resource
An attacker (or a shared-hosting scenario) serves a subresource, e.g. a stylesheet, with
Vary: Cookieso its body legitimately differs per session. - Prime the cache One document fetches the resource, and the response is stored in the shared subresource cache without any must-revalidate flag.
- Reuse across context A second document with different cookies requests the same URL and is served the stale, previously-cached body instead of a fresh, cookie-appropriate response.
- Observe the mismatch
The consuming document sees content computed from another context’s cookies, leaking state across the isolation boundary the
Varyheader was meant to enforce.
Impact Assessment
Vary: Cookie or Vary: * and is fetched by more than one document sharing the cache. Rated low severity and low impact, consistent with a logic/site-isolation flaw rather than memory corruption.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdom/base/nsContentUtils.cpp |
modified |
Files Changed
dom/base/nsContentUtils.cpp
Audit Directions
- Cache-key completenessReview other cross-document or shared caches to confirm they honor
Vary(especiallyCookieand*) before reusing a response, since a missingVarycheck silently breaks context isolation. - Header tokenizationAudit uses of
nsCCharSeparatedTokenizeron HTTP header values for correct trimming and case-insensitive matching so tokens likecookieare not missed due to whitespace or casing. - Revalidation defaultsCheck subresource-loading paths that default to cacheable-without-revalidation and ensure each accounts for authorization- or session-sensitive response variance before treating an entry as safely shareable.
Patch
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
index 6814ff6c951..2cd762c75b0 100644
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -13577,6 +13577,23 @@ nsContentUtils::GetSubresourceCacheValidationInfo(nsIRequest* aRequest,
if (!info.mMustRevalidate) {
(void)httpChannel->IsNoCacheResponse(&info.mMustRevalidate);
}
+
+ if (!info.mMustRevalidate) {
+ nsAutoCString vary;
+ (void)httpChannel->GetResponseHeader("vary"_ns, vary);
+ info.mMustRevalidate = [&] {
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(vary, ',').ToRange()) {
+ if (token.EqualsLiteral("*")) {
+ return true;
+ }
+ if (token.EqualsIgnoreCase("cookie")) {
+ return true;
+ }
+ }
+ return false;
+ }();
+ }
}
// data: URIs are safe to cache across documents under any circumstance, so we