Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in Core
DescriptionInformation leak in Core
ComponentCore
Bug ClassLogic Error
Tracker540059211
Fix commitf5383966e92c (chromium/src) +140/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
TEST_F
content/browser/declarative_performance_observer/declarative_performance_observer_unittest.cc
modified

Files Changed

  • content/browser/declarative_performance_observer/declarative_performance_observer.cc
  • content/browser/declarative_performance_observer/declarative_performance_observer_unittest.cc
From f5383966e92c1b7718d11931f3e471c56b7b9c5d Mon Sep 17 00:00:00 2001
From: Shunya Shishido <sisidovski@chromium.org>
Date: Mon, 03 Aug 2026 02:59:38 -0700
Subject: [PATCH] Sanitize URLs in DeclarativePerformanceObserver report bodies

In DeclarativePerformanceObserver, navigation URLs and Largest
Contentful Paint (LCP) resource URLs were previously serialized verbatim
into performance entry dictionaries within Reporting-API report bodies.
While ReportingServiceImpl::QueueReport() sanitizes top-level report
URLs by stripping fragments and user credentials, report bodies were
transmitted unmodified.

This change wraps URL assignments in
network::SerializeResponseUrlForReporting() when initializing and
updating committed_url_ from navigation handles (during initial
navigation and BackForwardCache restoration), as well as when formatting
LCP resource URLs in DidObservePerformanceEntries().

TAG=agy
CONV=0d216dbe-f50c-4f85-a83b-851e23cebd72

Bug: 540059211
Change-Id: I24f0e23291232f1aab23023db36fab0e47253a19
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8177145
Reviewed-by: Yoshisato Yanagisawa <yyanagisawa@chromium.org>
Commit-Queue: Shunya Shishido <sisidovski@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1672507}
---

diff --git a/content/browser/declarative_performance_observer/declarative_performance_observer.cc b/content/browser/declarative_performance_observer/declarative_performance_observer.cc
index 59c9da2..d958fe1 100644
--- a/content/browser/declarative_performance_observer/declarative_performance_observer.cc
+++ b/content/browser/declarative_performance_observer/declarative_performance_observer.cc
@@ -18,6 +18,7 @@
 #include "mojo/public/cpp/bindings/message.h"
 #include "net/base/load_timing_info.h"
 #include "net/base/net_errors.h"
+#include "services/network/public/cpp/url_util.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
@@ -51,7 +52,8 @@
   }
 
   navigation_start_ = navigation_handle->NavigationStart();
-  committed_url_ = navigation_handle->GetURL();
+  committed_url_ =
+      network::SerializeResponseUrlForReporting(navigation_handle->GetURL());
 
   network_anonymization_key_ =
       rfh->GetIsolationInfoForSubresources().network_anonymization_key();
@@ -151,6 +153,8 @@
   DCHECK(navigation_handle->IsServedFromBackForwardCache());
 
   navigation_start_ = navigation_handle->NavigationStart();
+  committed_url_ =
+      network::SerializeResponseUrlForReporting(navigation_handle->GetURL());
   buffered_entries_.clear();
   current_buffer_bytes_ = 0;
 
@@ -369,7 +373,16 @@
         dict.Set("renderTime", lcp->render_time.InMillisecondsF());
         dict.Set("loadTime", lcp->load_time.InMillisecondsF());
         dict.Set("id", lcp->id.value_or(""));
-        dict.Set("url", lcp->url.value_or(""));
+        std::string lcp_url = "";
+        if (lcp->url.has_value() && !lcp->url->empty()) {
+          GURL url(lcp->url.value());
+          if (url.is_valid()) {
+            lcp_url = network::SerializeResponseUrlForReporting(url).spec();
+          } else {
+            lcp_url = lcp->url.value();
+          }
+        }
+        dict.Set("url", lcp_url);
         dict.Set("element", lcp->element.value_or(""));
 
         AddEntryToBuffer(std::move(dict));
diff --git a/content/browser/declarative_performance_observer/declarative_performance_observer_unittest.cc b/content/browser/declarative_performance_observer/declarative_performance_observer_unittest.cc
index b2f5001..37cc7f61 100644
--- a/content/browser/declarative_performance_observer/declarative_performance_observer_unittest.cc
+++ b/content/browser/declarative_performance_observer/declarative_performance_observer_unittest.cc
@@ -1353,5 +1353,130 @@
   SetBrowserClientForTesting(old_client);
 }
 
+TEST_F(DeclarativePerformanceObserverTest,
+       SanitizesReportUrlsInNavigationEntry) {
+  const GURL kPageURL("https://user:password@example.com/index.html#section1");
+  const GURL kExpectedSanitizedURL("https://example.com/index.html");
+  const std::string kEndpoint("telemetry");
+
+  auto policy = network::mojom::DeclarativePerformanceObserverPolicy::New();
+  policy->reporting_endpoint = kEndpoint;
+  policy->entry_types.push_back(
+      network::mojom::PerformanceEntryType::kNavigation);
+
+  MockNavigationHandle navigation_handle(kPageURL, main_rfh());
+  navigation_handle.set_has_committed(true);
+  navigation_handle.set_is_in_primary_main_frame(true);
+  navigation_handle.set_is_error_page(false);
+
+  NavigationHandleTiming timing;
+  ON_CALL(navigation_handle, GetNavigationHandleTiming())
+      .WillByDefault(testing::ReturnRef(timing));
+
+  ON_CALL(navigation_handle, GetDeclarativePerformanceObserverPolicy())
+      .WillByDefault(testing::Return(policy.get()));
+
+  CreateObserver(&navigation_handle);
+  auto* observer =
+      DeclarativePerformanceObserver::GetForCurrentDocument(main_rfh());
+  ASSERT_TRUE(observer);
+
+  observer->OnEnterBFCache();
+
+  ASSERT_EQ(network_context_.reports().size(), 1u);
+  const auto& report1 = network_context_.reports()[0];
+  EXPECT_EQ(report1.type, "performance-observer");
+
+  const base::ListValue* entries1 = report1.body.FindList("entries");
+  ASSERT_TRUE(entries1);
+  ASSERT_GE(entries1->size(), 1u);
+
+  const base::DictValue* nav_entry1 = (*entries1)[0].GetIfDict();
+  ASSERT_TRUE(nav_entry1);
+  EXPECT_EQ(*(nav_entry1->FindString("entryType")), "navigation");
+  EXPECT_EQ(*(nav_entry1->FindString("name")), kExpectedSanitizedURL.spec());
+
+  network_context_.ClearReports();
+
+  const GURL kBFCacheURL(
+      "https://user:password@example.com/index.html#section2");
+  MockNavigationHandle restore_handle(kBFCacheURL, main_rfh());
+  restore_handle.set_has_committed(true);
+  restore_handle.set_is_in_primary_main_frame(true);
+  restore_handle.set_is_error_page(false);
+  restore_handle.set_is_served_from_bfcache(true);
+
+  observer->OnDidFinishNavigation(&restore_handle);
+
+  DeclarativePerformanceObserver::DeleteForCurrentDocument(main_rfh());
+
+  ASSERT_EQ(network_context_.reports().size(), 1u);
+  const auto& report2 = network_context_.reports()[0];
+  const base::ListValue* entries2 = report2.body.FindList("entries");
+  ASSERT_TRUE(entries2);
+  ASSERT_GE(entries2->size(), 1u);
+
+  const base::DictValue* nav_entry2 = (*entries2)[0].GetIfDict();
+  ASSERT_TRUE(nav_entry2);
+  EXPECT_EQ(*(nav_entry2->FindString("entryType")), "navigation");
+  EXPECT_EQ(*(nav_entry2->FindString("type")), "back_forward");
+  EXPECT_EQ(*(nav_entry2->FindString("name")), kExpectedSanitizedURL.spec());
+}
+
+TEST_F(DeclarativePerformanceObserverTest, SanitizesReportUrlsInLcpEntry) {
+  const GURL kPageURL("https://example.com/index.html");
+  const std::string kEndpoint("telemetry");
+
+  auto policy = network::mojom::DeclarativePerformanceObserverPolicy::New();
+  policy->reporting_endpoint = kEndpoint;
+  policy->entry_types.push_back(
+      network::mojom::PerformanceEntryType::kLargestContentfulPaint);
+
+  MockNavigationHandle navigation_handle(kPageURL, main_rfh());
+  navigation_handle.set_has_committed(true);
+  navigation_handle.set_is_in_primary_main_frame(true);
+  navigation_handle.set_is_error_page(false);
+
+  NavigationHandleTiming timing;
+  ON_CALL(navigation_handle, GetNavigationHandleTiming())
+      .WillByDefault(testing::ReturnRef(timing));
+
+  ON_CALL(navigation_handle, GetDeclarativePerformanceObserverPolicy())
+      .WillByDefault(testing::Return(policy.get()));
+
+  CreateObserver(&navigation_handle);
+
+  mojo::Remote<blink::mojom::DeclarativePerformanceObserverHost>
+      observer_remote;
+  DeclarativePerformanceObserver::Bind(
+      main_rfh(), observer_remote.BindNewPipeAndPassReceiver());
+
+  std::vector<blink::mojom::DeclarativePerformanceEntryPtr> entries;
+  entries.push_back(blink::mojom::DeclarativePerformanceEntry::NewLcp(
+      blink::mojom::DeclarativeLargestContentfulPaint::New(
+          base::Milliseconds(150), 450, base::Milliseconds(150),
+          base::Milliseconds(120), "hero-img",
+          "https://user:password@example.com/hero.png#section2", "IMG")));
+  observer_remote->DidObservePerformanceEntries(std::move(entries));
+  observer_remote.FlushForTesting();
+
+  DeclarativePerformanceObserver::DeleteForCurrentDocument(main_rfh());
+
+  ASSERT_EQ(network_context_.reports().size(), 1u);
+  const auto& report = network_context_.reports()[0];
+
+  const base::ListValue* entries_list = report.body.FindList("entries");
+  ASSERT_TRUE(entries_list);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/declarative_performance_observer/declarative_performance_observer_unittest.cc b/content/browser/declarative_performance_observer/declarative_performance_observer_unittest.cc
index b2f5001..37cc7f61 100644
--- a/content/browser/declarative_performance_observer/declarative_performance_observer_unittest.cc
+++ b/content/browser/declarative_performance_observer/declarative_performance_observer_unittest.cc
@@ -1353,5 +1353,130 @@
   SetBrowserClientForTesting(old_client);
 }
 
+TEST_F(DeclarativePerformanceObserverTest,
+       SanitizesReportUrlsInNavigationEntry) {
+  const GURL kPageURL("https://user:password@example.com/index.html#section1");
+  const GURL kExpectedSanitizedURL("https://example.com/index.html");
+  const std::string kEndpoint("telemetry");
+
+  auto policy = network::mojom::DeclarativePerformanceObserverPolicy::New();
+  policy->reporting_endpoint = kEndpoint;
+  policy->entry_types.push_back(
+      network::mojom::PerformanceEntryType::kNavigation);
+
+  MockNavigationHandle navigation_handle(kPageURL, main_rfh());
+  navigation_handle.set_has_committed(true);
+  navigation_handle.set_is_in_primary_main_frame(true);
+  navigation_handle.set_is_error_page(false);
+
+  NavigationHandleTiming timing;
+  ON_CALL(navigation_handle, GetNavigationHandleTiming())
+      .WillByDefault(testing::ReturnRef(timing));
+
+  ON_CALL(navigation_handle, GetDeclarativePerformanceObserverPolicy())
+      .WillByDefault(testing::Return(policy.get()));
+
+  CreateObserver(&navigation_handle);
+  auto* observer =
+      DeclarativePerformanceObserver::GetForCurrentDocument(main_rfh());
+  ASSERT_TRUE(observer);
+
+  observer->OnEnterBFCache();
+
+  ASSERT_EQ(network_context_.reports().size(), 1u);
+  const auto& report1 = network_context_.reports()[0];
+  EXPECT_EQ(report1.type, "performance-observer");
+
+  const base::ListValue* entries1 = report1.body.FindList("entries");
+  ASSERT_TRUE(entries1);
+  ASSERT_GE(entries1->size(), 1u);
+
+  const base::DictValue* nav_entry1 = (*entries1)[0].GetIfDict();
+  ASSERT_TRUE(nav_entry1);
+  EXPECT_EQ(*(nav_entry1->FindString("entryType")), "navigation");
+  EXPECT_EQ(*(nav_entry1->FindString("name")), kExpectedSanitizedURL.spec());
+
+  network_context_.ClearReports();
+
+  const GURL kBFCacheURL(
+      "https://user:password@example.com/index.html#section2");
+  MockNavigationHandle restore_handle(kBFCacheURL, main_rfh());
+  restore_handle.set_has_committed(true);
+  restore_handle.set_is_in_primary_main_frame(true);
+  restore_handle.set_is_error_page(false);
+  restore_handle.set_is_served_from_bfcache(true);
+
+  observer->OnDidFinishNavigation(&restore_handle);
+
+  DeclarativePerformanceObserver::DeleteForCurrentDocument(main_rfh());
+
+  ASSERT_EQ(network_context_.reports().size(), 1u);
+  const auto& report2 = network_context_.reports()[0];
+  const base::ListValue* entries2 = report2.body.FindList("entries");
+  ASSERT_TRUE(entries2);
+  ASSERT_GE(entries2->size(), 1u);
+
+  const base::DictValue* nav_entry2 = (*entries2)[0].GetIfDict();
+  ASSERT_TRUE(nav_entry2);
+  EXPECT_EQ(*(nav_entry2->FindString("entryType")), "navigation");
+  EXPECT_EQ(*(nav_entry2->FindString("type")), "back_forward");
+  EXPECT_EQ(*(nav_entry2->FindString("name")), kExpectedSanitizedURL.spec());
+}
+
+TEST_F(DeclarativePerformanceObserverTest, SanitizesReportUrlsInLcpEntry) {
+  const GURL kPageURL("https://example.com/index.html");
+  const std::string kEndpoint("telemetry");
+
+  auto policy = network::mojom::DeclarativePerformanceObserverPolicy::New();
+  policy->reporting_endpoint = kEndpoint;
+  policy->entry_types.push_back(
+      network::mojom::PerformanceEntryType::kLargestContentfulPaint);
+
+  MockNavigationHandle navigation_handle(kPageURL, main_rfh());
+  navigation_handle.set_has_committed(true);
+  navigation_handle.set_is_in_primary_main_frame(true);
+  navigation_handle.set_is_error_page(false);
+
+  NavigationHandleTiming timing;
+  ON_CALL(navigation_handle, GetNavigationHandleTiming())
+      .WillByDefault(testing::ReturnRef(timing));
+
+  ON_CALL(navigation_handle, GetDeclarativePerformanceObserverPolicy())
+      .WillByDefault(testing::Return(policy.get()));
+
+  CreateObserver(&navigation_handle);
+
+  mojo::Remote<blink::mojom::DeclarativePerformanceObserverHost>
+      observer_remote;
+  DeclarativePerformanceObserver::Bind(
+      main_rfh(), observer_remote.BindNewPipeAndPassReceiver());
+
+  std::vector<blink::mojom::DeclarativePerformanceEntryPtr> entries;
+  entries.push_back(blink::mojom::DeclarativePerformanceEntry::NewLcp(
+      blink::mojom::DeclarativeLargestContentfulPaint::New(
+          base::Milliseconds(150), 450, base::Milliseconds(150),
+          base::Milliseconds(120), "hero-img",
+          "https://user:password@example.com/hero.png#section2", "IMG")));
+  observer_remote->DidObservePerformanceEntries(std::move(entries));
+  observer_remote.FlushForTesting();
+
+  DeclarativePerformanceObserver::DeleteForCurrentDocument(main_rfh());
+
+  ASSERT_EQ(network_context_.reports().size(), 1u);
+  const auto& report = network_context_.reports()[0];
+
+  const base::ListValue* entries_list = report.body.FindList("entries");
+  ASSERT_TRUE(entries_list);
+  ASSERT_GE(entries_list->size(), 1u);
+
+  const base::Value& entry_val0 = (*entries_list)[0];
+  const base::DictValue* lcp_entry = entry_val0.GetIfDict();
+  ASSERT_TRUE(lcp_entry);
+
+  const std::string* url = lcp_entry->FindString("url");
+  ASSERT_TRUE(url);
+  EXPECT_EQ(*url, "https://example.com/hero.png");
+}
+
 }  // namespace
 }  // namespace content
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.