Low chrome Memory Corruption 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in WebProtect
DescriptionMemory corruption in WebProtect
ComponentWebProtect
Sub-componentData Controls
Bug ClassMemory Corruption
Tracker532987588
Fix commitfc9908e21ed6 (chromium/src) +227/-44
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.cc
modified

Files Changed

  • chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_unittest.cc
  • chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.cc
  • chrome/browser/enterprise/data_protection/print_utils.cc
  • chrome/browser/enterprise/data_protection/data_protection_utils.h
  • chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_types.cc
  • chrome/browser/enterprise/BUILD.gn
  • content/public/browser/clipboard_types.cc
  • chrome/browser/enterprise/data_protection/data_protection_utils.cc
  • chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
  • chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h
From fc9908e21ed6bdeb36c6ea8b4bacef92357aa231 Mon Sep 17 00:00:00 2001
From: Dominique Fauteux-Chapleau <[email protected]>
Date: Thu, 24 Sep 2026 12:12:18 -0700
Subject: [PATCH] [Data Controls] Use precursor origin URL for blank/uncommitted frames

When a popup is opened with a non-committing URL such as
`window.open("javascript:void(0)")`, the popup remains on its initial
empty document and `RenderFrameHost::GetLastCommittedURL()` returns an
empty/invalid `GURL`. Similarly, `window.open("about:blank")` commits
`about:blank` as its URL while inheriting the opener's security origin.

Because Data Controls and Enterprise Connectors checks relied solely on
`GetLastCommittedURL()`, popups on an initial empty document or
`about:blank` could bypass site-specific rules (e.g. `urls:
["source.com"]`) as well as wildcard rules (`urls: ["*"]`, since
`url_matcher::URLMatcher` does not match invalid `GURL`s and
`CreateDataEndpoint()` returned `std::nullopt` before invoking
`MaybeOverrideSourceURLForClipboardAccess()`).

This CL:
1. Introduces a shared `GetUrlFromRenderFrameHost()` helper in
`enterprise_data_protection` that resolves the main frame's last
committed URL, falling back to
`GetLastCommittedOrigin().GetTupleOrPrecursorTupleIfOpaque().GetURL()`
(or `about:blank` when no valid origin tuple exists) when the
committed URL is invalid, `about:blank`, or `about:srcdoc`.

2. Updates `MaybeOverrideSourceURLForClipboardAccess()`,
`DataProtectionNavigationObserver::ApplyDataProtectionSettings()`,
and `GetPrintAnalysisData()` to use `GetUrlFromRenderFrameHost()`.

3. Moves the `!source_url.is_valid()` check in
`content::CreateDataEndpoint()` to run after
`MaybeOverrideSourceURLForClipboardAccess()` so uncommitted documents
can still receive an overridden `DataTransferEndpoint` URL.

Fixed: 532987588
Change-Id: I7726712b0d6ed51f38b9fe51db56ef00d7db439e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8424128
Reviewed-by: Avi Drissman <[email protected]>
Auto-Submit: Dominique Fauteux-Chapleau <[email protected]>
Commit-Queue: Avi Drissman <[email protected]>
Commit-Queue: Dominique Fauteux-Chapleau <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1704494}
---
diff --git a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.cc b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.cc
index bb419ed..66938bba 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.cc
@@ -24,9 +24,9 @@
 #include "chrome/browser/enterprise/data_controls/chrome_clipboard_context.h"
 #include "chrome/browser/enterprise/data_controls/chrome_rules_service.h"
 #include "chrome/browser/enterprise/data_controls/data_controls_dialog_factory.h"
+#include "chrome/browser/enterprise/data_protection/data_protection_utils.h"
 #include "chrome/browser/enterprise/data_protection/paste_allowed_request.h"
 #include "chrome/browser/profiles/profile.h"
-#include "components/dom_distiller/core/url_utils.h"
 #include "components/enterprise/common/files_scan_data.h"
 #include "components/enterprise/connectors/core/connectors_prefs.h"
 #include "components/enterprise/connectors/core/features.h"
@@ -56,6 +56,7 @@
 #include "ui/base/data_transfer_policy/data_transfer_policy_controller.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
+#include "url/url_constants.h"
 
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
 #include "chrome/browser/printing/print_preview_dialog_controller.h"
@@ -95,19 +96,14 @@
   if (!endpoint.data_transfer_endpoint() ||
       !endpoint.data_transfer_endpoint()->IsUrlType() ||
       !endpoint.data_transfer_endpoint()->GetURL()) {
+    if (endpoint.render_frame_host()) {
+      return GetUrlFromRenderFrameHost(endpoint.render_frame_host());
+    }
     return GURL();
   }
   return *endpoint.data_transfer_endpoint()->GetURL();
 }
 
-GURL GetSourceURL(content::RenderFrameHost* rfh) {
-  auto url = rfh->GetMainFrame()->GetLastCommittedURL();
-  if (dom_distiller::url_utils::IsDistilledPage(url)) {
-    url = dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(url);
-  }
-  return url;
-}
-
 bool SkipDataControlOrContentAnalysisChecks(
     const content::ClipboardEndpoint& main_endpoint) {
   // Data Controls and content analysis copy/paste checks require an active tab
@@ -405,11 +401,11 @@
           Profile::FromBrowserContext(destination->GetBrowserContext())) {
     email = enterprise_connectors::ContentAreaUserProvider::GetUser(
         profile, content::WebContents::FromRenderFrameHost(destination),
-        GetSourceURL(destination));
+        GetUrlFromRenderFrameHost(destination));
   }
 
-  router->ReportPasteFromGemini(GetSourceURL(destination), email, verdict,
-                                content_size, bypassed);
+  router->ReportPasteFromGemini(GetUrlFromRenderFrameHost(destination), email,
+                                verdict, content_size, bypassed);
 #endif  // BUILDFLAG(SAFE_BROWSING_AVAILABLE)
 }
 
@@ -876,7 +872,7 @@
   }
 
   content::RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame();
-  auto url = GetSourceURL(rfh);
+  auto url = GetUrlFromRenderFrameHost(rfh);
   if (!url.is_valid()) {
     return std::nullopt;
   }
@@ -907,7 +903,7 @@
     // inside a cross-origin iframe.
     if (profile &&
         enterprise_connectors::ContentAnalysisDelegate::IsEnabled(
-            profile, GetSourceURL(destination), &dialog_data,
+            profile, GetUrlFromRenderFrameHost(destination), &dialog_data,
             enterprise_connectors::AnalysisConnector::BULK_DATA_ENTRY)) {
       dialog_data.text.push_back(std::move(data));
       dialog_data.reason =
@@ -1456,7 +1452,7 @@
   }
 
   ui::DataTransferEndpoint dte(
-      GetSourceURL(rfh),
+      GetUrlFromRenderFrameHost(rfh),
       {.off_the_record = rfh->GetBrowserContext()->IsOffTheRecord()});
   content::ClipboardEndpoint clipboard_endpoint =
       content::ClipboardEndpoint::ForFrame(
@@ -1511,7 +1507,7 @@
     if (rules_service) {
       base::ElapsedTimer timer;
       auto verdict = rules_service->GetPasteFromGeminiInChromeVerdict(
-          GetSourceURL(destination));
+          GetUrlFromRenderFrameHost(destination));
       base::UmaHistogramTimes(
           "Enterprise.DataControls.GlicPaste.EvaluationLatency",
           timer.Elapsed());
@@ -1586,20 +1582,39 @@
                                             std::move(callback));
 }
 
+GURL GetUrlFromRenderFrameHost(content::RenderFrameHost* rfh) {
+  if (!rfh) {
+    return GURL();
+  }
+  auto* main_frame = rfh->GetMainFrame();
+  auto url = main_frame->GetLastCommittedURL();
+  if (!url.is_valid() || url.IsAboutBlank() || url.IsAboutSrcdoc()) {
+    auto origin_url = main_frame->GetLastCommittedOrigin()
+                          .GetTupleOrPrecursorTupleIfOpaque()
+                          .GetURL();
+    if (origin_url.is_valid()) {
+      url = std::move(origin_url);
+    } else if (!url.is_valid()) {
+      url = GURL(url::kAboutBlankURL);
+    }
+  }
+  return GetOriginalUrl(url);
+}
+
 std::optional<GURL> MaybeOverrideSourceURLForClipboardAccess(
     content::RenderFrameHost* render_frame_host,
     const GURL& original_url) {
   DCHECK(render_frame_host);
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
   if (printing::PrintPreviewDialogController::IsPrintPreviewURL(original_url)) {
-    return printing::PrintPreviewDialogController::GetInstance()
-        ->GetInitiator(
-            content::WebContents::FromRenderFrameHost(render_frame_host))
-        ->GetPrimaryMainFrame()
-        ->GetLastCommittedURL();
+    return GetUrlFromRenderFrameHost(
+        printing::PrintPreviewDialogController::GetInstance()
+            ->GetInitiator(
+                content::WebContents::FromRenderFrameHost(render_frame_host))
+            ->GetPrimaryMainFrame());
   }
 #endif  // BUILDFLAG(ENABLE_PRINT_PREVIEW)
-  return std::nullopt;
+  return GetUrlFromRenderFrameHost(render_frame_host);
 }
 
 }  // namespace enterprise_data_protection
diff --git a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h
index 79e734c..af037e9f6 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h
+++ b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h
@@ -208,8 +208,15 @@
 void CopyTextToClipboard(content::RenderFrameHost* rfh,
                          const std::u16string& text);
 
-// Returns the initiator main frame's last committed URL if the `original_url`
-// is a Print Preview URL. Returns std::nullopt otherwise.
+// Returns the URL to use for data protection checks for `rfh`. If the main
+// frame's last committed URL is invalid, about:blank, or about:srcdoc, this
+// falls back to the main frame's precursor/tuple origin URL (or about:blank if
+// no valid origin tuple exists), and unwraps distilled page URLs.
+GURL GetUrlFromRenderFrameHost(content::RenderFrameHost* rfh);
+
+// Returns the desired URL for clipboard source/destination reporting for
+// `render_frame_host`, including resolving the initiator URL when
+// `original_url` is a Print Preview URL.
 std::optional<GURL> MaybeOverrideSourceURLForClipboardAccess(
     content::RenderFrameHost* render_frame_host,
     const GURL& original_url);
diff --git a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_types.cc b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_types.cc
index 6fa3e62..44b40f2 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_types.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_types.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_types.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h"
 #include "chrome/browser/glic/host/guest_util.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/clipboard_types.h"
@@ -87,6 +88,15 @@
     const content::ClipboardEndpoint& source) {
   BasicPasteSource cached;
   cached.data_transfer_endpoint = source.data_transfer_endpoint();
+  if (!cached.data_transfer_endpoint && source.render_frame_host()) {
+    cached.data_transfer_endpoint = ui::DataTransferEndpoint(
+        GetUrlFromRenderFrameHost(source.render_frame_host()),
+        {
+            .off_the_record = source.render_frame_host()
+                                  ->GetBrowserContext()
+                                  ->IsOffTheRecord(),
+        });
+  }
   if (source.browser_context()) {
     cached.browser_context = source.browser_context()->GetWeakPtr();
   }
diff --git a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
index 32fe9a344..f36d5f2f 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
@@ -15,14 +15,14 @@
 #include "base/time/time.h"
 #include "chrome/browser/enterprise/connectors/connectors_service.h"
 #include "chrome/browser/enterprise/data_controls/chrome_rules_service.h"
+#include "chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h"
 #include "chrome/browser/enterprise/data_protection/data_protection_features.h"
 #include "chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_factory.h"
+#include "chrome/browser/enterprise/data_protection/data_protection_utils.h"
 #include "chrome/browser/interstitials/enterprise_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/common/webui_url_constants.h"
-#include "components/dom_distiller/core/url_constants.h"
-#include "components/dom_distiller/core/url_utils.h"
 #include "components/enterprise/data_protection/data_protection_url_lookup_service.h"
 #include "components/enterprise/data_protection/utils.h"
 #include "components/safe_browsing/buildflags.h"
@@ -98,17 +98,6 @@
 }
 #endif  // BUILDFLAG(SAFE_BROWSING_AVAILABLE)
 
-GURL GetOriginalUrl(const GURL& url) {
-  if (GURL got_url =
-          dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(url);
-      got_url.is_valid()) {
-    return got_url;
-  } else {
-    VLOG(1) << __func__ << " got a invalid url: " << got_url;
-  }
-  return url;
-}
-
 void RunPendingNavigationCallback(
     content::WebContents* web_contents,
     DataProtectionNavigationObserver::Callback callback) {
@@ -313,14 +302,15 @@
     return;
   }
 
-  if (!web_contents->GetLastCommittedURL().is_valid()) {
+  const GURL original_url =
+      GetUrlFromRenderFrameHost(web_contents->GetPrimaryMainFrame());
+  if (!original_url.is_valid()) {
     std::move(callback).Run(UrlSettings::None());
     return;
   }
 
   std::string identifier = GetIdentifier(profile);
 
-  const GURL original_url = GetOriginalUrl(web_contents->GetLastCommittedURL());
   DataProtectionPageUserData::UpdateDataControlsScreenshotState(
       GetPageFromWebContents(web_contents), identifier,
       IsScreenshotAllowedByDataControls(profile, original_url));
diff --git a/chrome/browser/enterprise/data_protection/data_protection_utils.cc b/chrome/browser/enterprise/data_protection/data_protection_utils.cc
new file mode 100644
index 0000000..93490f1
--- /dev/null
+++ b/chrome/browser/enterprise/data_protection/data_protection_utils.cc
@@ -0,0 +1,24 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/data_protection/data_protection_utils.h"
+
+#include "base/logging.h"
+#include "components/dom_distiller/core/url_utils.h"
+#include "url/gurl.h"
+
+namespace enterprise_data_protection {
+
+GURL GetOriginalUrl(const GURL& url) {
+  if (GURL original_url =
+          dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(url);
+      original_url.is_valid()) {
+    return original_url;
+  } else {
+    VLOG(1) << __func__ << " got a invalid url: " << original_url;
+  }
+  return url;
+}
+
+}  // namespace enterprise_data_protection
diff --git a/chrome/browser/enterprise/data_protection/data_protection_utils.h b/chrome/browser/enterprise/data_protection/data_protection_utils.h
new file mode 100644
index 0000000..cd9a6fc
--- /dev/null
+++ b/chrome/browser/enterprise/data_protection/data_protection_utils.h
@@ -0,0 +1,20 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_UTILS_H_
+#define CHROME_BROWSER_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_UTILS_H_
+
+#include "url/gurl.h"
+
+namespace enterprise_data_protection {
+
+// Returns the URL that data protection checks should apply to for `url`. If
+// `url` is a distilled page URL (chrome-distiller://), the original URL of the
+// distilled page is returned so that policies apply to the page the content
+// actually comes from. `url` is returned as-is otherwise.
+GURL GetOriginalUrl(const GURL& url);
+
+}  // namespace enterprise_data_protection
+
+#endif  // CHROME_BROWSER_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_UTILS_H_
diff --git a/chrome/browser/enterprise/data_protection/print_utils.cc b/chrome/browser/enterprise/data_protection/print_utils.cc
index cb424c0d6..e578ada 100644
--- a/chrome/browser/enterprise/data_protection/print_utils.cc
+++ b/chrome/browser/enterprise/data_protection/print_utils.cc
@@ -11,6 +11,7 @@
 #include "base/containers/span.h"
 #include "base/feature_list.h"
 #include "base/memory/read_only_shared_memory_region.h"
+#include "chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/device_event_log/device_event_log.h"
 #include "components/enterprise/buildflags/buildflags.h"
@@ -149,7 +150,8 @@
 
   bool enabled = enterprise_connectors::ContentAnalysisDelegate::IsEnabled(
       Profile::FromBrowserContext(web_contents->GetBrowserContext()),
-      web_contents->GetOutermostWebContents()->GetLastCommittedURL(),
+      GetUrlFromRenderFrameHost(
+          web_contents->GetOutermostWebContents()->GetPrimaryMainFrame()),
       &scanning_data, enterprise_connectors::AnalysisConnector::PRINT);
 
   if (enabled && ShouldScan(context)) {
diff --git a/content/public/browser/clipboard_types.cc b/content/public/browser/clipboard_types.cc
index daa934c..6a0aa70 100644
--- a/content/public/browser/clipboard_types.cc
+++ b/content/public/browser/clipboard_types.cc
@@ -237,10 +237,6 @@
     RenderFrameHost& rfh) {
   auto* render_frame_host_main_frame = rfh.GetMainFrame();
   auto source_url = render_frame_host_main_frame->GetLastCommittedURL();
-  if (!source_url.is_valid()) {
-    return std::nullopt;
-  }
-
   if (auto maybe_url = GetContentClient()
                            ->browser()
                            ->MaybeOverrideSourceURLForClipboardAccess(
@@ -248,6 +244,10 @@
     source_url = *maybe_url;
   }
 
+  if (!source_url.is_valid()) {
+    return std::nullopt;
+  }
+
   return ui::DataTransferEndpoint(
       source_url,
       ui::DataTransferEndpointOptions{
diff --git a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_unittest.cc b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_unittest.cc
... (file diff truncated)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_unittest.cc b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_unittest.cc
index 9869109..21eb6e5 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_unittest.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_unittest.cc
@@ -28,6 +28,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/drop_data.h"
 #include "content/public/test/browser_task_environment.h"
+#include "content/public/test/navigation_simulator.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/public/test/web_contents_tester.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -1550,4 +1551,116 @@
   EXPECT_FALSE(PrepopulateFindBarTextAllowed(source, destination_blocked));
 }
 
+class DataProtectionClipboardInitialEmptyDocumentPopupTest
+    : public DataProtectionClipboardTest {
+ public:
+  void SetUp() override {
+    DataProtectionClipboardTest::SetUp();
+    scoped_features_.InitWithFeatures(
+        {data_controls::kDataControlsSearchWith,
+         enterprise_connectors::kContentAnalysisClipboardCopy},
+        {});
+    opener_web_contents_ =
+        content::WebContentsTester::CreateTestWebContents(profile_, nullptr);
+    content::WebContentsTester::For(opener_web_contents_.get())
+        ->NavigateAndCommit(GURL("https://source.com/page.html"));
+
+    popup_web_contents_ = content::WebContentsTester::CreateTestWebContents(
+        profile_, opener_web_contents_->GetSiteInstance());
+    auto simulator = content::NavigationSimulator::CreateRendererInitiated(
+        GURL(url::kAboutBlankURL), popup_web_contents_->GetPrimaryMainFrame());
+    simulator->SetInitiatorFrame(opener_web_contents_->GetPrimaryMainFrame());
+    simulator->Commit();
+
+    ASSERT_TRUE(popup_web_contents_->GetLastCommittedURL().IsAboutBlank());
+    ASSERT_EQ(
+        popup_web_contents_->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
+        url::Origin::Create(GURL("https://source.com")));
+  }
+
+  void SetBlockCopyingFromSourceURLRule() {
+    data_controls::SetDataControls(profile_->GetPrefs(), {R"({
+        "sources": {
+          "urls": ["source.com"]
+        },
+        "restrictions": [
+          {"class": "CLIPBOARD", "level": "BLOCK"}
+        ]
+    })"});
+  }
+
+  void SetBlockCopyingFromWildcardURLRule() {
+    data_controls::SetDataControls(profile_->GetPrefs(), {R"({
+        "sources": {
+          "urls": ["*"]
+        },
+        "restrictions": [
+          {"class": "CLIPBOARD", "level": "BLOCK"}
+        ]
+    })"});
+  }
+
+ protected:
+  content::RenderViewHostTestEnabler test_render_host_factories_;
+  std::unique_ptr<content::WebContents> opener_web_contents_;
+  std::unique_ptr<content::WebContents> popup_web_contents_;
+};
+
+TEST_F(DataProtectionClipboardInitialEmptyDocumentPopupTest,
+       CopyBlockedBySourceURLRule) {
+  SetBlockCopyingFromSourceURLRule();
+
+  auto endpoint = content::CreateClipboardEndpoint(
+      *popup_web_contents_->GetPrimaryMainFrame());
+  ASSERT_TRUE(endpoint.data_transfer_endpoint());
+  ASSERT_TRUE(endpoint.data_transfer_endpoint()->GetURL());
+  EXPECT_EQ(*endpoint.data_transfer_endpoint()->GetURL(),
+            GURL("https://source.com/"));
+
+  base::test::TestFuture<const ui::ClipboardFormatType&,
+                         const content::ClipboardPasteData&,
+                         std::optional<std::u16string>>
+      future;
+  IsClipboardCopyAllowedByPolicy(endpoint, CopyMetadata(),
+                                 MakeClipboardPasteData("confidential", "", {}),
+                                 future.GetCallback());
+  auto data = future.Get<1>();
+  EXPECT_TRUE(data.empty());
+  EXPECT_FALSE(IsClipboardCopyAllowedByPolicyForUI(popup_web_contents_.get()));
+  EXPECT_FALSE(IsSearchWithAllowed(popup_web_contents_.get()));
+  EXPECT_FALSE(CanPopulateFindBarFromSelection(popup_web_contents_.get()));
+}
+
+TEST_F(DataProtectionClipboardInitialEmptyDocumentPopupTest,
+       CopyBlockedByWildcardURLRuleOnUncommittedTabWithoutOpener) {
+  SetBlockCopyingFromWildcardURLRule();
+
+  std::unique_ptr<content::WebContents> uncommitted_web_contents =
+      content::WebContentsTester::CreateTestWebContents(profile_, nullptr);
+  ASSERT_TRUE(uncommitted_web_contents->GetLastCommittedURL().is_empty());
+  ASSERT_TRUE(uncommitted_web_contents->GetPrimaryMainFrame()
+                  ->GetLastCommittedOrigin()
+                  .opaque());
+
+  auto endpoint = content::CreateClipboardEndpoint(
+      *uncommitted_web_contents->GetPrimaryMainFrame());
+  ASSERT_TRUE(endpoint.data_transfer_endpoint());
+  ASSERT_TRUE(endpoint.data_transfer_endpoint()->GetURL());
+  EXPECT_EQ(*endpoint.data_transfer_endpoint()->GetURL(),
+            GURL(url::kAboutBlankURL));
+
+  base::test::TestFuture<const ui::ClipboardFormatType&,
+                         const content::ClipboardPasteData&,
+                         std::optional<std::u16string>>
+      future;
+  IsClipboardCopyAllowedByPolicy(endpoint, CopyMetadata(),
+                                 MakeClipboardPasteData("confidential", "", {}),
+                                 future.GetCallback());
+  auto data = future.Get<1>();
+  EXPECT_TRUE(data.empty());
+  EXPECT_FALSE(
+      IsClipboardCopyAllowedByPolicyForUI(uncommitted_web_contents.get()));
+  EXPECT_FALSE(IsSearchWithAllowed(uncommitted_web_contents.get()));
+}
+
 }  // namespace enterprise_data_protection
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.