Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in SiteIsolation
DescriptionImproper input validation in SiteIsolation
ComponentSiteIsolation
Bug ClassLogic Error
Tracker496595299
Fix commitcbef3d0f82fd (chromium/src) +10/-60
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
BindLambdaForTesting
content/browser/preloading/prerender/prerender_host_registry_unittest.cc
modified
TEST_F
content/browser/preloading/prerender/prerender_host_registry_unittest.cc
modified
if
content/browser/renderer_host/render_frame_host_impl.cc
modified

Files Changed

  • content/browser/preloading/prerender/prerender_host_registry_unittest.cc
  • content/browser/renderer_host/render_frame_host_impl.cc
  • content/browser/renderer_host/render_frame_host_impl.h
  • content/common/navigation_client.mojom
  • content/renderer/render_frame_impl.cc
  • content/test/navigation_simulator_impl.cc
  • content/test/navigation_simulator_impl.h
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
Loading diff…

Regression Test / PoC

shipped with the fix
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
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cc
  • content/browser/renderer_host/browsing_context_state.cc
  • third_party/blink/renderer/core/frame/local_dom_window.cc
  • third_party/blink/renderer/core/frame/remote_frame.cc
  • third_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

  1. Renderer Spoofing: When a navigation commits, the renderer sends a DidCommitProvisionalLoadParams IPC containing the has_potentially_trustworthy_unique_origin flag. A compromised renderer can maliciously set this to true for an opaque origin (e.g., a sandboxed <iframe>).
  2. Lack of Browser Validation: In content/browser/renderer_host/render_frame_host_impl.cc, within RenderFrameHostImpl::DidNavigate, the browser process extracts this flag directly from the IPC parameters and passes it to SetLastCommittedOrigin. Crucially, RenderFrameHostImpl::ValidateDidCommitParams does not validate this specific flag.
    // render_frame_host_impl.cc:5220
    SetLastCommittedOrigin(params.origin,
                           params.has_potentially_trustworthy_unique_origin);
    
    A TODO comment in SetLastCommittedOrigin explicitly acknowledges that the browser should calculate this value itself using network::IsOriginPotentiallyTrustworthy() instead of trusting the renderer.
  3. State Replication: SetLastCommittedOrigin calls BrowsingContextState::SetCurrentOrigin, which stores the poisoned flag in the browser’s replication_state_ and immediately broadcasts it to all RenderFrameProxyHosts in other renderer processes.
  4. Poisoning the RemoteFrame: In a clean renderer process (e.g., one hosting a cross-origin child frame), RemoteFrame::SetReplicatedOrigin receives the broadcast. It copies the origin and applies the poisoned flag by calling security_origin->SetOpaqueOriginIsPotentiallyTrustworthy(true).
  5. Bypassing the Ancestor Check: When the clean child frame evaluates its secure context status, LocalDOMWindow::HasInsecureContextInAncestors walks up the frame tree. For the spoofed ancestor’s RemoteFrame, it calls SecurityOrigin::IsPotentiallyTrustworthy(). Because the origin is opaque, this method returns the poisoned is_opaque_origin_potentially_trustworthy_ flag (true).
  6. Erroneous API Access: The descendant frame’s secure_context_mode_ is set to kSecureContext. 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.)

  1. 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'>.
  2. mid.html embeds a cross-origin victim iframe: <iframe src='https://victim.example/child.html'>. Due to Site Isolation, child.html is in a different process.
  3. Exploit a vulnerability in the renderer hosting mid.html to achieve RCE.
  4. During the navigation commit for mid.html, use the RCE to modify the DidCommitProvisionalLoadParams IPC, setting has_potentially_trustworthy_unique_origin = true.
  5. The browser receives the IPC, updates its state, and broadcasts the poisoned flag to the clean renderer process hosting child.html.
  6. When child.html commits, its LocalDOMWindow::HasInsecureContextInAncestors check will evaluate the spoofed RemoteFrame as trustworthy.
  7. window.isSecureContext will be true in child.html. If child.html attempts 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.

View on issue tracker