CVE-2026-87553
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
BindLambdaForTestingcontent/browser/preloading/prerender/prerender_host_registry_unittest.cc |
modified | |
TEST_Fcontent/browser/preloading/prerender/prerender_host_registry_unittest.cc |
modified | |
ifcontent/browser/renderer_host/render_frame_host_impl.cc |
modified |
Files Changed
content/browser/preloading/prerender/prerender_host_registry_unittest.cccontent/browser/renderer_host/render_frame_host_impl.cccontent/browser/renderer_host/render_frame_host_impl.hcontent/common/navigation_client.mojomcontent/renderer/render_frame_impl.cccontent/test/navigation_simulator_impl.cccontent/test/navigation_simulator_impl.h
Patch
From cbef3d0f82fdb884a10fe58f5cb75fcc3eef50fd Mon Sep 17 00:00:00 2001
From: Rakina Zata Amni <rakina@chromium.org>
Date: Sun, 02 Aug 2026 20:56:10 -0700
Subject: [PATCH] Remove DidCommitParams has_potentially_trustworthy_unique_origin
Instead of passing has_potentially_trustworthy_unique_origin from the renderer and clamping it in the browser, this CL removes the field from DidCommitProvisionalLoadParams entirely and computes it in the browser process inside RenderFrameHostImpl::SetLastCommittedOrigin.
This is a security improvement as it prevents a compromised renderer from claiming that an opaque origin is potentially trustworthy.
Bug: 496595299
TAG=agy
CONV=16ca3df5-92fb-4b33-a9bf-29956e520fc5
Change-Id: I032fc6c526130cb431d9772693c398795142b6c9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8181587
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: Sam McNally <sammc@chromium.org>
Commit-Queue: Rakina Zata Amni <rakina@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1672472}
---
diff --git a/content/browser/preloading/prerender/prerender_host_registry_unittest.cc b/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
index cdaf2fcd..cd144968 100644
--- a/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
+++ b/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
@@ -1796,19 +1796,6 @@
insecure_navigations);
}
-TEST_F(PrerenderHostRegistryTest,
- HasPotentiallyTrustworthyUniqueOriginIsSetWhilePrerendering) {
- SetupPrerenderAndCommit(
- base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) {
- navigation->set_has_potentially_trustworthy_unique_origin(true);
- }));
- EXPECT_TRUE(contents()
- ->GetPrimaryMainFrame()
- ->frame_tree_node()
- ->current_replication_state()
- .has_potentially_trustworthy_unique_origin);
-}
-
// End replication state matching tests ------------
TEST_F(PrerenderHostRegistryTest, OneTaskToDeleteAllHosts) {
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 59c3e37..48e7b97f 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -5301,8 +5301,7 @@
// The origin is only updated for cross-document navigations.
if (!was_within_same_document ||
!features::IsEnforceSameDocumentOriginInvariantsEnabled()) {
- SetLastCommittedOrigin(params.origin,
- params.has_potentially_trustworthy_unique_origin);
+ SetLastCommittedOrigin(params.origin);
}
// If the navigation was a cross-document navigation and it's not the
@@ -5441,14 +5440,13 @@
}
}
-void RenderFrameHostImpl::SetLastCommittedOrigin(
- const url::Origin& origin,
- bool is_potentially_trustworthy_unique_origin) {
+void RenderFrameHostImpl::SetLastCommittedOrigin(const url::Origin& origin) {
last_committed_origin_ = origin;
- // TODO(https://crbug.com/40159049): Instead of passing
- // `is_potentially_trustworthy_unique_origin`, maybe we can just check if the
- // origin is opaque and use ``network::IsOriginPotentiallyTrustworthy()` on
- // its precursor origin.
+ const url::SchemeHostPort& precursor =
+ origin.GetTupleOrPrecursorTupleIfOpaque();
+ bool is_potentially_trustworthy_unique_origin =
+ origin.opaque() && precursor.IsValid() &&
+ network::IsUrlPotentiallyTrustworthy(precursor.GetURL());
browsing_context_state()->SetCurrentOrigin(
origin, is_potentially_trustworthy_unique_origin);
}
@@ -5459,13 +5457,7 @@
void RenderFrameHostImpl::SetLastCommittedOriginForTesting(
const url::Origin& origin) {
- // Default setting `is_potentially_trustworthy_unique_origin` to just whether
- // the origin is opaque or not, since we don't really have a way to get the
- // correct value from a random origin. Since this function is used mostly for
- // unit tests that won't actually use this value (which is only used in the
- // renderer), it should be good enough.
- SetLastCommittedOrigin(
- origin, /*is_potentially_trustworthy_unique_origin=*/origin.opaque());
+ SetLastCommittedOrigin(origin);
}
const url::Origin& RenderFrameHostImpl::ComputeTopFrameOrigin(
@@ -5824,19 +5816,7 @@
GetStoragePartition()->IncrementActiveDocumentCount(
GetNetworkIsolationKey());
}
- // The `is_potentially_trustworthy_unique_origin` bit should be inherited from
- // the creator frame if it exists. Note that we do this even when the new
- // frame is sandboxed, following `DocumentLoader::CaclculateOrigin()`.
- // TODO(https://crbug.com/40159049): Once we can always trust
- // `network::IsOriginPotentiallyTrustworthy()` instead of passing around
- // `has_potentially_trustworthy_unique_origin`, remove this.
- bool is_potentially_trustworthy_unique_origin =
- creator_frame ? creator_frame->browsing_context_state()
- ->current_replication_state()
- .has_potentially_trustworthy_unique_origin
- : false;
- SetLastCommittedOrigin(new_frame_origin,
- is_potentially_trustworthy_unique_origin);
+ SetLastCommittedOrigin(new_frame_origin);
if (!creator_frame || !creator_frame->is_error_document_) {
frame_tree_node()->set_last_successful_origin(new_frame_origin);
}
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index aa51bc7..2b56bf69 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -4010,8 +4010,7 @@
// Update this frame's last committed origin. This will also update the origin
// and the "has_potentially_trustworthy_unique_origin" bit in the
// FrameReplicationState.
- void SetLastCommittedOrigin(const url::Origin& origin,
- bool is_potentially_trustworthy_unique_origin);
+ void SetLastCommittedOrigin(const url::Origin& origin);
// Stores a snapshot of the inherited base URL from the initiator's
// FrameLoadRequest, if this document inherited one (e.g., about:srcdoc).
diff --git a/content/common/navigation_client.mojom b/content/common/navigation_client.mojom
index e038cee..f6becf5 100644
--- a/content/common/navigation_client.mojom
+++ b/content/common/navigation_client.mojom
@@ -159,10 +159,6 @@
// enforcing.
array<uint32> insecure_navigations_set;
- // True if the document for the load is a unique origin that should be
- // considered potentially trustworthy.
- bool has_potentially_trustworthy_unique_origin;
-
// Request ID generated by the renderer.
int32 request_id;
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 746d0107..2dda49f 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -5178,9 +5178,6 @@
params->insecure_request_policy = frame_->GetInsecureRequestPolicy();
params->insecure_navigations_set = frame_->GetInsecureRequestToUpgrade();
- params->has_potentially_trustworthy_unique_origin =
- frame_origin.IsOpaque() && frame_origin.IsPotentiallyTrustworthy();
-
// Set the URL to be displayed in the browser UI to the user. Note this might
// be different than the URL actually used in the DocumentLoader (see comments
// in GetLoadingUrl() and MaybeGetOverriddenURL()). This might not be the URL
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index d3200ab..fa8b6b1 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -1723,8 +1723,6 @@
params->insecure_request_policy = insecure_request_policy_;
params->insecure_navigations_set = insecure_navigations_set_;
- params->has_potentially_trustworthy_unique_origin =
- has_potentially_trustworthy_unique_origin_;
params->commit_navigation_start = base::TimeTicks::Now();
params->commit_navigation_end = base::TimeTicks::Now();
diff --git a/content/test/navigation_simulator_impl.h b/content/test/navigation_simulator_impl.h
index 7bf8fc1..90c8ee2 100644
--- a/content/test/navigation_simulator_impl.h
+++ b/content/test/navigation_simulator_impl.h
@@ -217,12 +217,6 @@
insecure_navigations_set_ = insecure_navigations_set;
}
- void set_has_potentially_trustworthy_unique_origin(
- bool has_potentially_trustworthy_unique_origin) {
- has_potentially_trustworthy_unique_origin_ =
- has_potentially_trustworthy_unique_origin;
- }
-
void set_supports_loading_mode_header(std::string value) {
supports_loading_mode_header_ = value;
}
@@ -405,7 +399,6 @@
blink::mojom::InsecureRequestPolicy insecure_request_policy_ =
blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone;
std::vector<uint32_t> insecure_navigations_set_;
- bool has_potentially_trustworthy_unique_origin_ = false;
// Any DNS aliases, as read from CNAME records, for the request URL that
// would be in the network::mojom::URLResponseHead. The alias chain order
Regression Test / PoC
diff --git a/content/browser/preloading/prerender/prerender_host_registry_unittest.cc b/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
index cdaf2fcd..cd144968 100644
--- a/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
+++ b/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
@@ -1796,19 +1796,6 @@
insecure_navigations);
}
-TEST_F(PrerenderHostRegistryTest,
- HasPotentiallyTrustworthyUniqueOriginIsSetWhilePrerendering) {
- SetupPrerenderAndCommit(
- base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) {
- navigation->set_has_potentially_trustworthy_unique_origin(true);
- }));
- EXPECT_TRUE(contents()
- ->GetPrimaryMainFrame()
- ->frame_tree_node()
- ->current_replication_state()
- .has_potentially_trustworthy_unique_origin);
-}
-
// End replication state matching tests ------------
TEST_F(PrerenderHostRegistryTest, OneTaskToDeleteAllHosts) {
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index d3200ab..fa8b6b1 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -1723,8 +1723,6 @@
params->insecure_request_policy = insecure_request_policy_;
params->insecure_navigations_set = insecure_navigations_set_;
- params->has_potentially_trustworthy_unique_origin =
- has_potentially_trustworthy_unique_origin_;
params->commit_navigation_start = base::TimeTicks::Now();
params->commit_navigation_end = base::TimeTicks::Now();
diff --git a/content/test/navigation_simulator_impl.h b/content/test/navigation_simulator_impl.h
index 7bf8fc1..90c8ee2 100644
--- a/content/test/navigation_simulator_impl.h
+++ b/content/test/navigation_simulator_impl.h
@@ -217,12 +217,6 @@
insecure_navigations_set_ = insecure_navigations_set;
}
- void set_has_potentially_trustworthy_unique_origin(
- bool has_potentially_trustworthy_unique_origin) {
- has_potentially_trustworthy_unique_origin_ =
- has_potentially_trustworthy_unique_origin;
- }
-
void set_supports_loading_mode_header(std::string value) {
supports_loading_mode_header_ = value;
}
@@ -405,7 +399,6 @@
blink::mojom::InsecureRequestPolicy insecure_request_policy_ =
blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone;
std::vector<uint32_t> insecure_navigations_set_;
- bool has_potentially_trustworthy_unique_origin_ = false;
// Any DNS aliases, as read from CNAME records, for the request URL that
// would be in the network::mojom::URLResponseHead. The alias chain order
Original Bug Report
Potential: Compromised renderer can bypass Secure Context ancestor checks via spoofed replication state
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A compromised renderer can spoof the has_potentially_trustworthy_unique_origin flag during a navigation commit. The browser process blindly trusts this flag, stores it, and broadcasts it to sibling renderers. This allows a cross-process child frame to erroneously treat an insecure ancestor as a Secure Context, bypassing W3C requirements for restricted APIs like Service Workers.
Affected files:
content/browser/renderer_host/render_frame_host_impl.cccontent/browser/renderer_host/browsing_context_state.ccthird_party/blink/renderer/core/frame/local_dom_window.ccthird_party/blink/renderer/core/frame/remote_frame.ccthird_party/blink/renderer/platform/weborigin/security_origin.cc
Estimated timestamp from git blame: 2025-07-10
Summary
A compromised renderer process can send a spoofed has_potentially_trustworthy_unique_origin boolean in the DidCommitProvisionalLoadParams IPC during a navigation commit. The browser process accepts this value without validation and broadcasts it to other renderer processes.
This flaw allows a cross-process descendant frame, which should be considered an insecure context due to an insecure ancestor, to incorrectly obtain isSecureContext=true. This erroneously grants the child frame access to sensitive APIs (e.g., Service Workers, WebAuthn) that are gated behind W3C Secure Contexts requirements (§3.1), completely bypassing the ancestor-chain security check.
Vulnerability Details
- Renderer Spoofing: When a navigation commits, the renderer sends a
DidCommitProvisionalLoadParamsIPC containing thehas_potentially_trustworthy_unique_originflag. A compromised renderer can maliciously set this totruefor an opaque origin (e.g., a sandboxed<iframe>). - Lack of Browser Validation: In
content/browser/renderer_host/render_frame_host_impl.cc, withinRenderFrameHostImpl::DidNavigate, the browser process extracts this flag directly from the IPC parameters and passes it toSetLastCommittedOrigin. Crucially,RenderFrameHostImpl::ValidateDidCommitParamsdoes not validate this specific flag.A TODO comment in// render_frame_host_impl.cc:5220 SetLastCommittedOrigin(params.origin, params.has_potentially_trustworthy_unique_origin);SetLastCommittedOriginexplicitly acknowledges that the browser should calculate this value itself usingnetwork::IsOriginPotentiallyTrustworthy()instead of trusting the renderer. - State Replication:
SetLastCommittedOrigincallsBrowsingContextState::SetCurrentOrigin, which stores the poisoned flag in the browser’sreplication_state_and immediately broadcasts it to allRenderFrameProxyHosts in other renderer processes. - Poisoning the RemoteFrame: In a clean renderer process (e.g., one hosting a cross-origin child frame),
RemoteFrame::SetReplicatedOriginreceives the broadcast. It copies the origin and applies the poisoned flag by callingsecurity_origin->SetOpaqueOriginIsPotentiallyTrustworthy(true). - Bypassing the Ancestor Check: When the clean child frame evaluates its secure context status,
LocalDOMWindow::HasInsecureContextInAncestorswalks up the frame tree. For the spoofed ancestor’sRemoteFrame, it callsSecurityOrigin::IsPotentiallyTrustworthy(). Because the origin is opaque, this method returns the poisonedis_opaque_origin_potentially_trustworthy_flag (true). - Erroneous API Access: The descendant frame’s
secure_context_mode_is set tokSecureContext. The clean renderer now erroneously exposes Secure-Context-gated APIs (like Service Workers) to the child frame, despite it having an insecure ancestor.
Furthermore, browser-side checks for API access often fail to re-verify the ancestor chain. For example, ServiceWorkerContainerHostForClient::Register only checks if the frame’s own origin is potentially trustworthy, relying on the renderer’s isSecureContext evaluation for the ancestor chain requirement.
Potential Reproduction Steps
(Note: These are suggested steps; a working PoC has not been executed yet.)
- Host a top-level page (
https://top.example) that embeds an attacker-controlled, sandboxed iframe:<iframe sandbox='allow-scripts' src='http://attacker.example/mid.html'>. mid.htmlembeds a cross-origin victim iframe:<iframe src='https://victim.example/child.html'>. Due to Site Isolation,child.htmlis in a different process.- Exploit a vulnerability in the renderer hosting
mid.htmlto achieve RCE. - During the navigation commit for
mid.html, use the RCE to modify theDidCommitProvisionalLoadParamsIPC, settinghas_potentially_trustworthy_unique_origin = true. - The browser receives the IPC, updates its state, and broadcasts the poisoned flag to the clean renderer process hosting
child.html. - When
child.htmlcommits, itsLocalDOMWindow::HasInsecureContextInAncestorscheck will evaluate the spoofedRemoteFrameas trustworthy. window.isSecureContextwill betrueinchild.html. Ifchild.htmlattempts to register a Service Worker (e.g., via an XSS or its own legitimate code), the registration will succeed, compromising the victim’s origin.
Suggested Fix
Remove the has_potentially_trustworthy_unique_origin field from mojom::DidCommitProvisionalLoadParams. The browser process should exclusively determine the trustworthiness of an origin during navigation commits.
As noted in the existing TODO in RenderFrameHostImpl::SetLastCommittedOrigin (crbug.com/1153336 or crbug.com/40159049), the browser should check if the origin is opaque and use network::IsOriginPotentiallyTrustworthy() on its precursor origin, rather than trusting the potentially compromised renderer process.
Evaluated with Chrome root at commit: 0eb4855bda702feaaa8b899336664f97e3df88b8
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. Please feel free to reach out to me if you have concerns or feedback.