← All reports

Remove ResourceRequest.m_cachePartition

CVE: CVE-2026-64753 · Safari 27 · Released September 14, 2026 Impact: Processing maliciously crafted web content may disclose sensitive user information Apple's description: A permissions issue was addressed by removing the vulnerable code. Credit: Viggo Lekdorf

Severity: Medium | Component: WebCore network request layer / cache partitioning | 71730be | Bugzilla 315121

Medium 등급이며, 도달 가능한 최대치는 corruption이 아니라 정보 노출입니다. loader 입장에서는 항상 일치해야 하는 두 필드가 있습니다. 하나는 로드가 귀속되는 first party이고, 다른 하나는 그 로드가 들어갈 cache namespace입니다. 그런데 두 값을 각각 따로 설정할 수 있었고, 네 곳의 호출 지점이 저마다 다른 출처에서 두 번째 값을 만들어냈습니다. 둘이 어긋나는 순간 캐시 조회가 site 경계를 넘게 됩니다.

Web cache는 URL을 인덱스로 삼는 공유 상태입니다. 그래서 서로 무관한 site 사이를 잇는 side channel이 되기 쉽습니다. 요즘 엔진들이 공통으로 택한 대응은 cache key에 top-level site를 함께 넣어 키 공간을 넓히는 방식입니다. 이 경우 attacker.test 아래에서 가져온 https://cdn.example/lib.js와 bank.test 아래에서 가져온 동일 URL은 서로 다른 슬롯을 차지합니다. WebKit에서 이 확장 키는 request 객체가 직접 들고 다니는 문자열, 즉 registrable domain입니다. in-process MemoryCache와 Network process의 on-disk NetworkCache 양쪽 모두 이 값을 키로 사용합니다. 이 설계가 성립하려면 지켜져야 하는 invariant는 매우 좁고, 코드에는 명시되어 있지 않습니다. 해당 문자열은 언제나 firstPartyForCookies의 registrable domain이어야 한다는 조건입니다. firstPartyForCookies는 cookie 귀속과 same-site 판단을 위해 request가 이미 들고 있는 필드입니다.

관전 포인트: 패치 이전에는 request가 어떤 site를 first party로 내세우면서도, 정작 cache 조회는 다른 site의 partition에서 이루어질 수 있었습니다. 페이지가 방문한 적도 없는 top-level site의 항목을 읽거나, 거꾸로 그 자리에 항목을 심어 넣는 것도 가능해집니다.

Source/WebCore/platform/network/ResourceRequestBase.cpp

-void ResourceRequestBase::setCachePartition(const String& cachePartition)
+String ResourceRequestBase::cachePartition() const
{
#if ENABLE(CACHE_PARTITIONING)
 
- ASSERT(!cachePartition.isNull());
 
- ASSERT(cachePartition == partitionName(cachePartition));
 
- m_cachePartition = cachePartition;
-#else
 
- UNUSED_PARAM(cachePartition);
-#endif
-}
-
-String ResourceRequestBase::partitionName(const String& domain)
-{
 
- if (domain.isNull())
+ if (!m_shouldBlockThirdPartyStorage)
return emptyString();
 
- auto highLevel = PublicSuffixStore::singleton().topPrivatelyControlledDomain(domain);
 
- if (highLevel.isNull())
+ RegistrableDomain domain(firstPartyForCookies());
+ if (domain.isEmpty())
return emptyString();
 
- return highLevel;
+ return domain.string();
+#else
+ return emptyString();
+#endif
}
 
void ResourceRequestBase::setAsIsolatedCopy(const ResourceRequest& other)
{
...
 
- setCachePartition(other.cachePartition().isolatedCopy());
+ setShouldBlockThirdPartyStorage(other.shouldBlockThirdPartyStorage());

Source/WebCore/platform/network/ResourceRequestBase.h

 
- WEBCORE_EXPORT static String partitionName(const String& domain);
 
- const String& cachePartition() const LIFETIME_BOUND { return m_cachePartition; }
 
- WEBCORE_EXPORT void setCachePartition(const String&);
 
- void setDomainForCachePartition(const String& domain) { setCachePartition(partitionName(domain)); }
+ WEBCORE_EXPORT String cachePartition() const;
+ bool shouldBlockThirdPartyStorage() const { return m_shouldBlockThirdPartyStorage; }
+ void setShouldBlockThirdPartyStorage(bool value) { m_shouldBlockThirdPartyStorage = value; }
...
RequestData m_requestData;
String m_initiatorIdentifier;
 
- String m_cachePartition { emptyString() };
RefPtr<FormData> m_httpBody;
...
+ bool m_shouldBlockThirdPartyStorage : 1 { true };
bool m_hiddenFromInspector : 1;

Source/WebCore/dom/ScriptExecutionContext.cpp

String ScriptExecutionContext::domainForCachePartition() const
{
 
- if (!m_domainForCachePartition.isNull())
 
- return m_domainForCachePartition;
-
if (m_storageBlockingPolicy != StorageBlockingPolicy::BlockThirdParty)
return emptyString();
 
return protect(topOrigin())->domainForCachePartition();
}
 
+bool ScriptExecutionContext::shouldBlockThirdPartyStorage() const
+{
+ return m_storageBlockingPolicy == StorageBlockingPolicy::BlockThirdParty;
+}

Source/WebCore/page/Page.cpp

 
- if (document->settings().storageBlockingPolicy() != StorageBlockingPolicy::BlockThirdParty)
 
- document->setDomainForCachePartition(String { emptyString() });
 
- else
 
- document->setDomainForCachePartition(origin->domainForCachePartition());
+ document->setStorageBlockingPolicy(document->settings().storageBlockingPolicy());

Source/WebCore/platform/network/cocoa/ResourceRequestCocoa.mm

if (m_nsRequest) {
 
- RetainPtr<NSString> cachePartition = [NSURLProtocol propertyForKey:bridge_cast(_kCFURLCachePartitionKey) inRequest:m_nsRequest.get()];
 
- if (cachePartition)
 
- m_cachePartition = cachePartition.get();
+ RetainPtr cachePartition = dynamic_objc_cast<NSString>([NSURLProtocol propertyForKey:bridge_cast(_kCFURLCachePartitionKey) inRequest:m_nsRequest.get()]);
+ m_shouldBlockThirdPartyStorage = !![cachePartition length];
}

Source/WebKit/Shared/WebCoreArgumentCodersPlatform.serialization.in

[CreateUsing=fromResourceRequestData] class WebCore::ResourceRequest {
Variant<WebCore::ResourceRequest::RequestData, WebCore::ResourceRequestPlatformData> getRequestDataToSerialize();
 
- String cachePartition();
+ bool shouldBlockThirdPartyStorage();
bool hiddenFromInspector();
};

이번 변경의 핵심은 상태 구조를 줄이는 작업입니다. ResourceRequestBase에서 String m_cachePartition { emptyString() } 멤버가 제거되었고, 대신 비트 하나짜리 bool m_shouldBlockThirdPartyStorage : 1 { true }가 추가되었습니다. cachePartition()도 저장된 필드를 const String&로 반환하던 accessor에서 매번 값을 계산하는 getter로 바뀌었습니다. 비트가 clear 상태이면 emptyString()을 반환하고, 그렇지 않으면 RegistrableDomain domain(firstPartyForCookies())을 구성한 뒤 domain.string()을 반환합니다. 값이 호출 시점마다 새로 만들어지므로, CachedResource::cachePartition()의 반환 타입도 const String&에서 String으로 변경되었고 LIFETIME_BOUND annotation은 제거되었습니다.

partition을 독립적으로 기록할 수 있던 경로는 모두 삭제되었습니다. setCachePartition(const String&), PublicSuffixStore::singleton().topPrivatelyControlledDomain을 감싸던 static partitionName(const String&), 그리고 ResourceRequestBase와 CachedResourceRequest, ScriptExecutionContext에 있던 setDomainForCachePartition helper가 여기에 해당합니다. 제거의 여파는 네 갈래로 퍼집니다.

비트를 설정하는 호출 지점. ThreadableWebSocketChannel::webSocketConnectRequest, DOMURL::revokeObjectURL, InspectorResourceUtilities::cachedResource, CachedResourceLoader::requestUserCSSStyleSheet와 requestResource, XMLHttpRequest::createRequest, Internals::resourceFromMemoryCache, SWServer::createScriptRequest, NetworkResourceLoader::continueWillSendRequest가 모두 partition 문자열 대신 setShouldBlockThirdPartyStorage(...)를 호출하도록 변경되었습니다. LegacyWebArchive::createInternal에는 동작 자체가 추가되었습니다. 이제 frame.document()에 대한 null 검사를 거친 뒤 request.setFirstPartyForCookies(document->firstPartyForCookies())를 호출합니다. 새 getter가 요구하는 값이지만, partition만 설정하던 기존 경로에서는 한 번도 채워지지 않던 부분입니다.

Loader 분기의 축소. DocumentLoader::loadMainResource에 있던 세 갈래 분기가 사라졌습니다. 기존에는 subframe인 경우 main document의 partition을 복사했고, main frame인 경우 SecurityOrigin::create(mainResourceRequest.resourceRequest().url())로 새로 만들었습니다. 이 분기가 document 또는 settings().storageBlockingPolicy()에서 가져온 bool 하나로 정리되었습니다. 한편 AsyncRevalidation과 NetworkCacheSpeculativeLoadManager는 revalidation 요청에 key.partition()을 다시 적용하지 않습니다. speculative loader 쪽은 대신 !key.partition().isEmpty() 값으로 비트를 설정합니다.

ScriptExecutionContext의 정책 일원화. override 멤버였던 m_domainForCachePartition이 제거되었고, 정책 검사보다 먼저 이 값을 참조하던 early return도 함께 사라졌습니다. Page::setupForRemoteWorker는 더 이상 partition 문자열을 기록하지 않습니다. 대신 document->setStorageBlockingPolicy(document->settings().storageBlockingPolicy())로 정책 자체를 기록합니다.

IPC 표면. WebCoreArgumentCodersPlatform.serialization.in은 이제 String cachePartition() 대신 bool shouldBlockThirdPartyStorage()를 직렬화합니다. WebCore::ServiceWorkerJobData 역시 String domainForCachePartition이 아니라 bool shouldBlockThirdPartyStorage를 싣고 다니며, 생성자 시그니처와 isolatedCopy, ServiceWorkerContainer의 job 생성 지점 두 곳이 여기에 맞춰 수정되었습니다. IPC fuzzing layout test 두 개도 cachePartition: ''를 shouldBlockThirdPartyStorage: true로 변경했습니다.

Cocoa에서는 doUpdateResourceRequest가 embedder가 넘긴 partition을 아예 가져오지 않게 되었습니다. [NSURLProtocol propertyForKey:_kCFURLCachePartitionKey] 읽기는 dynamic_objc_cast<NSString>로 감싸졌고, 값 자체가 아니라 비어 있는지 여부만 남습니다(m_shouldBlockThirdPartyStorage = !![cachePartition length]). CFNetwork가 다시 전달하는 요청, 예를 들어 redirect에서 비트를 판별하는 데는 이 정도면 충분하며, http/tests/cache/disk-cache/disk-cache-redirect.html이 확인하는 지점이기도 합니다. _kCFURLCachePartitionKey 자체는 Network process 안에서 WebKit과 NSURLSession을 잇는 전달 통로로 그대로 남아 있습니다. 사라진 것은 앱이 건넨 NSURLRequest가 들고 있는 사본을 신뢰할 수 있는 값으로 취급하던 부분입니다.

마지막으로, 제거된 static에 의존하던 cache 삭제 helper 세 곳이 남습니다. MemoryCache::removeResourcesWithOrigin의 두 overload와 NetworkProcess::deleteWebsiteDataForOrigin이 여기에 해당합니다. 이 세 곳은 RegistrableDomain::uncheckedCreateFromHost(host)로 partition을 다시 계산하며, isEmpty() ? emptyString() : string() fallback을 명시적으로 둡니다. 수동으로 캐시되는 이미지용으로 domainForCachePartition 문자열을 받던 CachedImage 생성자는 통째로 삭제되었습니다.

Cache partitioning. WebKit은 캐시된 리소스를 partition key 단위로 구분합니다. 같은 URL이라도 최상위 페이지가 사이트 A일 때 받아온 리소스와 사이트 B에서 받아온 리소스는 서로 다른 캐시 슬롯을 차지하게 됩니다. 이 key는 문자열입니다. 이번 diff에서는 MemoryCache::removeResourcesWithOrigin(origin, partition)의 namespace 인자, NetworkCache::Key::partition(), 그리고 cache->traverse(cachePartition, ...)의 순회 필터로 등장합니다.

캐시는 둘, 키는 하나. MemoryCache는 WebContent 안에 있는 리소스 캐시로, CachedResourceLoader와 MemoryCache::resourceForRequest가 참조합니다. NetworkCache는 Network process가 디스크에 유지하는 HTTP 캐시입니다. 일반적인 페이지 로드에는 두 캐시가 모두 관여하며, 둘 다 요청의 partition을 키로 사용합니다.

RegistrableDomain. 호스트의 "top privately controlled domain"을 감싼 WebCore 타입입니다. 대략 public suffix에 레이블 하나를 더한 값이며(sub.example.co.uk → example.co.uk), 실제 계산은 PublicSuffixStore::singleton().topPrivatelyControlledDomain(host)가 담당합니다. RegistrableDomain::uncheckedCreateFromHost(host)는 URL을 다시 파싱하지 않고 호스트 문자열에서 바로 값을 생성합니다.

firstPartyForCookies. ResourceRequestBase의 필드로, 해당 로드가 귀속되는 주체의 URL을 담습니다. subresource라면 최상위 document가 여기에 해당합니다. 쿠키 정책과 same-site 판정은 이미 이 값을 참조하고 있습니다.

StorageBlockingPolicy. third-party storage 접근을 허용할지 표현하는 context 단위 enum입니다(AllowAll, BlockThirdParty 등). ScriptExecutionContext는 이 값을 m_storageBlockingPolicy에 보관하며, 생성자에서 StorageBlockingPolicy::AllowAll로 초기화한 뒤 보통 settings 값으로 덮어씁니다.

프로세스를 넘나드는 ResourceRequest. ResourceRequest는 WebContent에서 생성되어 Network process로 직렬화됩니다. 필드 목록은 Source/WebKit/Shared/WebCoreArgumentCodersPlatform.serialization.in에 정의되어 있고, Cocoa 변형은 수신 측의 ResourceRequest::fromResourceRequestData가 다시 조립합니다. 여기에 선언된 필드는 보내는 프로세스가 영향을 줄 수 있는 범위 안에 그대로 놓입니다.

_kCFURLCachePartitionKey. WebKit과 NSURLSession 사이에서 cache partition을 전달하는 데 쓰이는 CFNetwork NSURLRequest property key입니다. [NSURLProtocol propertyForKey:inRequest:]는 타입이 지정되지 않은 id를 반환하므로, WebKit은 그 값을 문자열로 사용하기 전에 dynamic_objc_cast<NSString>로 클래스를 확인하는 방식을 관용적으로 씁니다.

Remote worker page. Service worker는 Page::setupForRemoteWorker가 조립한 합성 Page/Document 위에서 동작합니다. 이 document에는 navigation을 거치지 않고 worker의 origin과 privacy 설정, referrer policy가 직접 주입됩니다. 한편 ServiceWorkerContainer::addRegistration과 updateRegistration은 WebContent에서 ServiceWorkerJobData를 만들어 SWServer로 전달합니다. SWServer는 이를 SWServer::createScriptRequest에서 script fetch용 ResourceRequest로 변환합니다.

보안에 직접 관여하는 lookup key가 source of truth에서 파생되지 않고, 별도로 변경 가능한 중복 상태로 저장되어 있었습니다. 전형적인 denormalization 버그에 해당합니다.

  Before:                            After:
  ResourceRequest                    ResourceRequest
   ├─ m_firstPartyForCookies ──┐      ├─ m_firstPartyForCookies ──┐
   └─ m_cachePartition         │      └─ m_shouldBlockThirdParty  │
        ▲    ▲     ▲     ▲     │           │                      │
   setCachePartition()  (must   │      cachePartition() ◄──────────┘
   setDomainForCache…()  agree) │        = RegistrableDomain(firstParty)
   NSURLRequest property        │
   originalRequest() copy ──────┘      (no storage → nothing to desync)

왼쪽이 패치 이전의 모습입니다. m_firstPartyForCookies와 m_cachePartition은 둘 다 request 상태이고, 코드베이스는 이 둘이 서로 일치할 것을 전제합니다. partition은 first party의 registrable domain이어야 한다는 조건입니다. 다만 이를 강제하는 장치는 없었습니다. setCachePartition()과 setDomainForCachePartition()이 public이었기 때문에, 일치 여부는 호출 지점마다 지켜야 하는 관례에 머물렀습니다. 삭제된 코드에서는 호출 지점들이 이 관례를 균일하게 지키지 않았다는 점이 드러납니다. DocumentLoader::loadMainResource는 main frame의 partition을 SecurityOrigin::create(mainResourceRequest.resourceRequest().url())에서 얻었는데, 이는 first party가 아니라 요청 자신의 URL입니다. LegacyWebArchive::createInternal은 partition만 설정하고 first party는 전혀 채우지 않았습니다. 이번 fix가 그 자리에 setFirstPartyForCookies를 추가해야 했던 이유입니다. NetworkResourceLoader::continueWillSendRequest는 redirect 이전의 partition을 redirect 이후 요청에 의도적으로 복사했습니다. AsyncRevalidation과 speculative load manager는 cache entry에서 가져온 key.partition()을 다시 적용했습니다. 필드는 하나인데 출처는 넷이었습니다.

invariant가 코드에 명시된 유일한 지점은, 이번 패치가 삭제한 setter 안이었습니다:

void ResourceRequestBase::setCachePartition(const String& cachePartition)
{
    ASSERT(!cachePartition.isNull());
    ASSERT(cachePartition == partitionName(cachePartition));
    m_cachePartition = cachePartition;
}

두 줄 모두 release build에서는 사라집니다. 그런데 이 필드는 IPC를 타고 들어오는 값입니다. WebCoreArgumentCodersPlatform.serialization.in은 ResourceRequest에 String cachePartition()을 선언하고 있었고, ServiceWorkerJobData는 registration 경로를 따라 String domainForCachePartition을 SWServer::createScriptRequest까지 실어 날랐습니다. 역직렬화된 데이터에 걸린 debug assertion은 invariant를 문서화할 뿐, 성립시키지는 못합니다. 같은 문자열에는 세 번째 유입 경로도 있었습니다. doUpdateResourceRequest가 embedder가 건넨 NSURLRequest에서 _kCFURLCachePartitionKey를 읽어 그 결과를 그대로 m_cachePartition에 대입했습니다. 타입이 지정되지 않은 id 값이었고, 클래스 확인도 거치지 않았습니다.

ScriptExecutionContext에는 같은 중복이 한 벌 더 들어 있었습니다. 이쪽은 실제로 어긋난 형태가 diff에 그대로 드러납니다:

  setupForRemoteWorker (pre-fix):
    m_storageBlockingPolicy   = AllowAll        ← constructor default, never written
    m_domainForCachePartition = origin->domainForCachePartition()
                                  │
    domainForCachePartition() ────┘  early-returns the override,
                                     never reaching the policy check

domainForCachePartition()은 override 값이 null이 아니기만 하면 m_domainForCachePartition을 반환했습니다. 그 아래에 있는 m_storageBlockingPolicy != StorageBlockingPolicy::BlockThirdParty 검사는 건너뛰게 됩니다. 그런데 Page::setupForRemoteWorker는 override만 기록하고 m_storageBlockingPolicy는 생성자 기본값인 AllowAll 그대로 두었습니다. 결과적으로 remote worker document는 한쪽 필드에서 partition을 가져오는 동안, 선언된 storage blocking policy는 다른 이야기를 하고 있었습니다. third-party storage 접근에 대한 권위가 둘로 나뉘어 있었고, 구조상 서로 어긋날 수밖에 없었습니다. 이번 fix는 setupForRemoteWorker가 정책 자체를 기록하도록 바꾸고 override는 완전히 삭제했습니다. 남은 권위는 정책 하나뿐입니다.

어긋남이 어떤 결과로 이어지는지는 partition의 용도에서 바로 따라 나옵니다. partition은 두 캐시 키의 namespace 성분입니다. MemoryCache::removeResourcesWithOrigin(origin, originPartition), NetworkCache::Key::partition(), cache->traverse(cachePartition, ...)가 모두 이 값을 사용합니다. 저장된 partition이 firstPartyForCookies가 가리키는 것과 다른 registrable domain을 지목하면, 저장이나 조회가 해당 로드의 실제 first-party context와는 다른 최상위 사이트의 partition에 떨어집니다. 사이트 A가 사이트 B의 캐시된 리소스를 관찰하거나 채워 넣지 못하도록 막는 경계가 그 요청에 대해서는 성립하지 않습니다.

여기서 따라 나오는 primitive는 cross-site cache read oracle입니다. 다른 사이트에 캐시된 리소스의 존재 여부와 타이밍을 관찰할 수 있고, 프로세스 내부 MemoryCache hit의 경우 캐시된 응답 내용까지 노출될 가능성이 있습니다. 여기에 더해, 사용자가 방문한 적 없는 사이트의 partition을 미리 채워 두는 것도 가능합니다. 영향은 사용자 데이터 노출입니다. 방문 기록과 캐시된 응답 본문이 여기에 해당하며, advisory의 impact 문구와도 일치합니다.

memory corruption primitive는 발생하지 않고, sandbox 경계가 넘어가지도 않습니다. 위험에 놓인 것은 site 경계이며, IPC 문자열이라는 지렛대는 WebContent process가 이미 장악된 상태를 전제합니다. diff가 확립하는 것은 어긋남이 가능한 상태 자체와, 그런 상태가 발생할 수 있는 구체적인 형태 두 가지입니다. remote worker 설정과, redirect를 건너 옮겨지는 partition이 그것입니다. 다만 content에서 시작해 끝까지 이어지는 어긋남이 실제로 시연된 것은 아닙니다. 그래서 이번 평가는 입증된 체인이 아니라 버그 클래스에 기대고 있습니다.

패치 이후에는 다이어그램의 오른쪽이 성립합니다. partition을 저장하는 공간이 없으니, 어긋날 대상 자체가 사라집니다. 제거 그 자체 외에 구조적으로 눈여겨볼 지점이 하나 더 있습니다. 멤버의 기본값이 방향을 바꿨다는 점입니다. String m_cachePartition { emptyString() }에서는 호출 지점이 partition 설정을 빠뜨리면 그 요청이 공유된 빈 partition으로 떨어졌습니다. fail-open에 해당합니다. 반면 bool m_shouldBlockThirdPartyStorage : 1 { true }와 파생 getter가 짝을 이루면, firstPartyForCookies가 설정되는 순간 그 요청은 곧바로 partition이 나뉩니다. 이 값은 loader가 쿠키 귀속을 위해 별도로 설정하는 값입니다. 결과적으로 fail-closed가 됩니다.

이 방향 전환은 공격 표면의 위치도 함께 옮깁니다. 이전에는 요청의 partition에 도달하는 경로가 setter 두 개뿐이었습니다. 이제는 firstPartyForCookies()의 순수 함수이므로, setFirstPartyForCookies()를 호출하는 모든 지점이 partition에 영향을 주게 됩니다. embedder delegate가 updateFromDelegatePreservingOldProperties()로 변형한 요청도, doUpdateResourceRequest를 거치는 redirect 같은 CFNetwork 생성 요청도 여기에 포함됩니다. 새 getter는 firstPartyForCookies 자체가 권위 있는 값이며 쓰기가 가능한 모든 지점에서 검증된다고 가정합니다. commit message는 이 근거를 문장으로 밝힐 뿐("which should always have the same RegistrableDomain"), getter에서 확인하지는 않습니다. firstPartyForCookies를 무관한 사이트로 설정할 수 있으면서 예전에는 partition을 건드릴 수 없던 경로가 존재한다면, 과거에는 쿠키 귀속만 어긋나게 하던 primitive에서 cross-partition cache read가 뒤따를 가능성이 있습니다. 그래서 이번 변경 이후 점검할 가치가 있는 영역은 delegate가 수정한 요청과 redirect에서 파생된 요청입니다.

cache partition을 요청의 first party에서 파생시키지 않고 따로 설정 가능한 문자열로 저장한 탓에, 로드의 캐시 조회가 해당 로드가 속하지 않은 최상위 사이트의 namespace에 떨어질 수 있었습니다.

cache partitioning은 문자열 키로 구현된 보안 경계입니다. 이번 commit은 그 버그 클래스 전체를 겨냥한 구조적 수정에 해당합니다. 키를 저장하지 말고 principal에서 파생시키라는 것입니다. 다른 엔진들도 같은 이유로 같은 방향을 택했습니다. Chromium은 임시방편식 cache key를 파생값인 NetworkIsolationKey로 대체했고, Firefox는 OriginAttributes에서 partitioning을 파생시킵니다. 파생되는 키를 동시에 독립적으로 설정할 수도 있게 두면, invariant는 호출 지점마다 새로 갱신해야 하는 코드 리뷰 의무로 바뀌기 때문입니다. 이번 diff에는 그 값을 각기 다른 출처에서 계산하던 호출 지점이 네 곳 등장합니다. 요청 URL, document의 top origin, cache entry의 key, 그리고 redirect 이전 요청이 그것입니다.