Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in DevTools
DescriptionInsufficient policy enforcement in DevTools
ComponentDevTools
Bug ClassLogic Error
Tracker496189510
Fix commitbf745058e2e8 (chromium/src) +8/-225
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

Changed Functions

FunctionChangeNotes
if
content/browser/devtools/protocol/storage_handler.cc
modified

Files Changed

  • content/browser/devtools/protocol/storage_handler.cc
  • third_party/blink/web_tests/http/tests/inspector-protocol/storage/interest-groups-expected.txt
From bf745058e2e8a43f342c49d5b985d793f0818bfb Mon Sep 17 00:00:00 2001
From: Danil Somsikov <dsv@chromium.org>
Date: Thu, 02 Apr 2026 05:38:20 -0700
Subject: [PATCH] Disable InterestGroup tracking and details in DevTools

The Protected Audience API is deprecated and the current implementation has potential security issues: `Storage.setInterestGroupTracking` registered a profile-wide observer, leaking Protected Audience activity across all tabs to any
DevTools client. Additionally, `Storage.getInterestGroupDetails` did not
check `MayAttachToURL()`, allowing a DevTools client (such as a Chrome
extension) to retrieve sensitive bidding data and browsing history from
domains it was explicitly blocked from accessing via enterprise
policies.

To address this issue:
- `StorageHandler::SetInterestGroupTracking` now returns success without
  registering the `InterestGroupManager` observer.
- `StorageHandler::GetInterestGroupDetails` now immediately returns an
  empty response, ensuring no sensitive data is leaked.
- Test expectations have been updated to reflect that
  `interestGroupAccessed` events and `interestGroupDetails` are no
  longer recorded or returned.

Fixed: 496189510
Change-Id: I32185011cb06cf7b1e89622f068151be71cc6252
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7715378
Commit-Queue: Danil Somsikov <dsv@chromium.org>
Auto-Submit: Danil Somsikov <dsv@chromium.org>
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1609132}
---

diff --git a/content/browser/devtools/protocol/storage_handler.cc b/content/browser/devtools/protocol/storage_handler.cc
index e240043..1c3b3bd 100644
--- a/content/browser/devtools/protocol/storage_handler.cc
+++ b/content/browser/devtools/protocol/storage_handler.cc
@@ -1112,87 +1112,26 @@
       bid, bid_currency.CopyAsOptional(), auction_id.CopyAsOptional());
 }
 
-namespace {
-void SendGetInterestGroup(
-    std::unique_ptr<StorageHandler::GetInterestGroupDetailsCallback> callback,
-    std::optional<SingleStorageInterestGroup> storage_group) {
-  if (!storage_group) {
-    callback->sendFailure(Response::ServerError("Interest group not found"));
-    return;
-  }
-
-  base::DictValue ig_serialization =
-      SerializeInterestGroupForDevtools(storage_group.value()->interest_group);
-
-  // "joiningOrigin" is in StorageInterestGroup, not InterestGroup, so it needs
-  // to be added in separately.
-  ig_serialization.Set("joiningOrigin",
-                       storage_group.value()->joining_origin.Serialize());
-  callback->sendSuccess(
-      std::make_unique<base::DictValue>(std::move(ig_serialization)));
-}
-
-}  // namespace
-
 void StorageHandler::GetInterestGroupDetails(
     const std::string& owner_origin_string,
     const std::string& name,
     std::unique_ptr<GetInterestGroupDetailsCallback> callback) {
-  if (!storage_partition_) {
-    callback->sendFailure(Response::InternalError());
-    return;
-  }
-
-  InterestGroupManagerImpl* manager = static_cast<InterestGroupManagerImpl*>(
-      storage_partition_->GetInterestGroupManager());
-  if (!manager) {
-    callback->sendFailure(
-        Response::ServerError("Interest group storage is disabled"));
-    return;
-  }
-
-  GURL owner_origin_url(owner_origin_string);
-  if (!owner_origin_url.is_valid()) {
-    callback->sendFailure(Response::ServerError("Invalid Owner Origin"));
-    return;
-  }
-  url::Origin owner_origin = url::Origin::Create(GURL(owner_origin_string));
-  DCHECK(!owner_origin.opaque());
-
-  manager->GetInterestGroup(
-      owner_origin, name,
-      base::BindOnce(&SendGetInterestGroup, std::move(callback)));
+  // TODO(crbug.com/496189510): Remove this completely once the DevTools
+  // frontend usage is gone.
+  callback->sendSuccess(std::make_unique<base::DictValue>());
 }
 
 Response StorageHandler::SetInterestGroupTracking(bool enable) {
-  interest_group_tracking_enabled_ = enable;
-  return SetInterestGroupTrackingInternal(storage_partition_, enable);
+  // TODO(crbug.com/496189510): Remove this completely once the DevTools
+  // frontend usage is gone.
+  return Response::Success();
 }
 
 Response StorageHandler::SetInterestGroupTrackingInternal(
     StoragePartition* storage_partition,
     bool enable) {
-  if (!storage_partition) {
-    return Response::InternalError();
-  }
-
-  InterestGroupManagerImpl* manager = static_cast<InterestGroupManagerImpl*>(
-      storage_partition->GetInterestGroupManager());
-  if (!manager) {
-    return Response::ServerError("Interest group storage is disabled.");
-  }
-
-  if (enable) {
-    // Only add if we are not already registered as an observer. We only
-    // observe the interest group manager, so if we're observing anything then
-    // we are already registered.
-    if (!InterestGroupManagerImpl::InterestGroupObserver::IsInObserverList()) {
-      manager->AddInterestGroupObserver(this);
-    }
-  } else {
-    // Removal doesn't care if we are not registered.
-    manager->RemoveInterestGroupObserver(this);
-  }
+  // TODO(crbug.com/496189510): Remove this completely once the DevTools
+  // frontend usage is gone.
   return Response::Success();
 }
 
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/interest-groups-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/interest-groups-expected.txt
index 5efa96a..aace1b8 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/interest-groups-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/interest-groups-expected.txt
@@ -45,162 +45,6 @@
     type : sellerJs
     url : https://a.test:8443/inspector-protocol/resources/fledge_decision_logic.js.php
 }
-interestGroupAccessed {
-    accessTime : <number>
-    name : 0
-    ownerOrigin : https://a.test:8443
-    type : join
-    uniqueAuctionId : global
-}
-interestGroupDetails {
-    ads : [
-        [0] : {
-            metadata : {"ad":"metadata","here":[1,2,3]}
-            renderURL : https://example.com/render0
-        }
-    ]
-    biddingLogicURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_logic.js.php
-    executionMode : compatibility
-    joiningOrigin : https://a.test:8443
-    name : 0
-    ownerOrigin : https://a.test:8443
-    priority : 0
-    trustedBiddingSignalsURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_signals.js.php
-}
-interestGroupAccessed {
-    accessTime : <number>
-    name : 1
-    ownerOrigin : https://a.test:8443
-    type : join
-    uniqueAuctionId : global
-}
-interestGroupDetails {
-    ads : [
-        [0] : {
-            metadata : {"ad":"metadata","here":[1,2,3]}
-            renderURL : https://example.com/render1
-        }
-    ]
-    biddingLogicURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_logic.js.php
-    executionMode : compatibility
-    joiningOrigin : https://a.test:8443
-    name : 1
-    ownerOrigin : https://a.test:8443
-    priority : 0
-    trustedBiddingSignalsURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_signals.js.php
-}
-interestGroupAccessed {
-    accessTime : <number>
-    name : 0
-    ownerOrigin : https://a.test:8443
-    type : loaded
-    uniqueAuctionId : 1
-}
-interestGroupDetails {
-    ads : [
-        [0] : {
-            metadata : {"ad":"metadata","here":[1,2,3]}
-            renderURL : https://example.com/render0
-        }
-    ]
-    biddingLogicURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_logic.js.php
-    executionMode : compatibility
-    joiningOrigin : https://a.test:8443
-    name : 0
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/interest-groups-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/interest-groups-expected.txt
index 5efa96a..aace1b8 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/interest-groups-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/interest-groups-expected.txt
@@ -45,162 +45,6 @@
     type : sellerJs
     url : https://a.test:8443/inspector-protocol/resources/fledge_decision_logic.js.php
 }
-interestGroupAccessed {
-    accessTime : <number>
-    name : 0
-    ownerOrigin : https://a.test:8443
-    type : join
-    uniqueAuctionId : global
-}
-interestGroupDetails {
-    ads : [
-        [0] : {
-            metadata : {"ad":"metadata","here":[1,2,3]}
-            renderURL : https://example.com/render0
-        }
-    ]
-    biddingLogicURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_logic.js.php
-    executionMode : compatibility
-    joiningOrigin : https://a.test:8443
-    name : 0
-    ownerOrigin : https://a.test:8443
-    priority : 0
-    trustedBiddingSignalsURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_signals.js.php
-}
-interestGroupAccessed {
-    accessTime : <number>
-    name : 1
-    ownerOrigin : https://a.test:8443
-    type : join
-    uniqueAuctionId : global
-}
-interestGroupDetails {
-    ads : [
-        [0] : {
-            metadata : {"ad":"metadata","here":[1,2,3]}
-            renderURL : https://example.com/render1
-        }
-    ]
-    biddingLogicURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_logic.js.php
-    executionMode : compatibility
-    joiningOrigin : https://a.test:8443
-    name : 1
-    ownerOrigin : https://a.test:8443
-    priority : 0
-    trustedBiddingSignalsURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_signals.js.php
-}
-interestGroupAccessed {
-    accessTime : <number>
-    name : 0
-    ownerOrigin : https://a.test:8443
-    type : loaded
-    uniqueAuctionId : 1
-}
-interestGroupDetails {
-    ads : [
-        [0] : {
-            metadata : {"ad":"metadata","here":[1,2,3]}
-            renderURL : https://example.com/render0
-        }
-    ]
-    biddingLogicURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_logic.js.php
-    executionMode : compatibility
-    joiningOrigin : https://a.test:8443
-    name : 0
-    ownerOrigin : https://a.test:8443
-    priority : 0
-    trustedBiddingSignalsURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_signals.js.php
-}
-interestGroupAccessed {
-    accessTime : <number>
-    name : 1
-    ownerOrigin : https://a.test:8443
-    type : loaded
-    uniqueAuctionId : 1
-}
-interestGroupDetails {
-    ads : [
-        [0] : {
-            metadata : {"ad":"metadata","here":[1,2,3]}
-            renderURL : https://example.com/render1
-        }
-    ]
-    biddingLogicURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_logic.js.php
-    executionMode : compatibility
-    joiningOrigin : https://a.test:8443
-    name : 1
-    ownerOrigin : https://a.test:8443
-    priority : 0
-    trustedBiddingSignalsURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_signals.js.php
-}
-interestGroupAccessed {
-    accessTime : <number>
-    bid : 1
-    name : 0
-    ownerOrigin : https://a.test:8443
-    type : bid
-    uniqueAuctionId : 1
-}
-interestGroupDetails {
-    ads : [
-        [0] : {
-            metadata : {"ad":"metadata","here":[1,2,3]}
-            renderURL : https://example.com/render0
-        }
-    ]
-    biddingLogicURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_logic.js.php
-    executionMode : compatibility
-    joiningOrigin : https://a.test:8443
-    name : 0
-    ownerOrigin : https://a.test:8443
-    priority : 0
-    trustedBiddingSignalsURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_signals.js.php
-}
-interestGroupAccessed {
-    accessTime : <number>
-    bid : 2
-    name : 1
-    ownerOrigin : https://a.test:8443
-    type : bid
-    uniqueAuctionId : 1
-}
-interestGroupDetails {
-    ads : [
-        [0] : {
-            metadata : {"ad":"metadata","here":[1,2,3]}
-            renderURL : https://example.com/render1
-        }
-    ]
-    biddingLogicURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_logic.js.php
-    executionMode : compatibility
-    joiningOrigin : https://a.test:8443
-    name : 1
-    ownerOrigin : https://a.test:8443
-    priority : 0
-    trustedBiddingSignalsURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_signals.js.php
-}
-interestGroupAccessed {
-    accessTime : <number>
-    name : 1
-    ownerOrigin : https://a.test:8443
-    type : win
-    uniqueAuctionId : 1
-}
-interestGroupDetails {
-    ads : [
-        [0] : {
-            metadata : {"ad":"metadata","here":[1,2,3]}
-            renderURL : https://example.com/render1
-        }
-    ]
-    biddingLogicURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_logic.js.php
-    executionMode : compatibility
-    joiningOrigin : https://a.test:8443
-    name : 1
-    ownerOrigin : https://a.test:8443
-    priority : 0
-    trustedBiddingSignalsURL : https://a.test:8443/inspector-protocol/resources/fledge_bidding_signals.js.php
-}
 Stop Tracking IG Events
 Logged IG events:
 interestGroupAuctionEventOccurred {
Loading diff…

Original Bug Report

reported by vi...@google.com

Potential cross-tab tracking and policy bypass via Storage.setInterestGroupTracking

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: The Storage.setInterestGroupTracking DevTools method registers a profile-wide observer that leaks Protected Audience activity across all tabs. Furthermore, Storage.getInterestGroupDetails fails to verify the target URL against enterprise policies using MayAttachToURL(). This allows a malicious extension to monitor sensitive bidding data and browsing history from restricted origins, bypassing tab isolation and enterprise policies.

Affected files:

  • content/browser/devtools/protocol/storage_handler.cc
  • content/browser/interest_group/interest_group_manager_impl.cc
  • content/browser/interest_group/interest_group_manager_impl.h

Estimated timestamp from git blame: 2024-11-25

Summary

There is a potential security vulnerability in the DevTools StorageHandler where a chrome.debugger extension attached to a single allowed tab can receive a real-time stream of Protected Audience (formerly FLEDGE) activity across the user’s entire browsing session. This bypasses enterprise runtime_blocked_hosts policies and tab isolation.

Technical Details

  1. Profile-Wide Observation: In content/browser/devtools/protocol/storage_handler.cc, the SetInterestGroupTrackingInternal method registers the StorageHandler as an observer on InterestGroupManagerImpl. Because InterestGroupManagerImpl is scoped to the StoragePartition (the entire profile), the observer listens for all interest group events globally.
  2. Unfiltered Broadcasting: When an interest group event occurs in any tab, InterestGroupManagerImpl::NotifyInterestGroupAccessed iterates through all observers unconditionally. It does not filter by RenderFrameHost or tab ID. The StorageHandler::OnInterestGroupAccessed then forwards the event details (including owner_origin and name) to the DevTools frontend.
  3. Missing Authorization Check: The StorageHandler::GetInterestGroupDetails method retrieves the full interest group configuration using an (owner_origin, name) pair. Unlike other sensitive methods in StorageHandler (e.g., GetCookies, which filters results through NetworkHandler::CanAccessCookie and client_->MayAttachToURL()), GetInterestGroupDetails entirely omits the MayAttachToURL() check.

Because ExtensionDevToolsClientHost overrides MayAttachToURL to enforce the runtime_blocked_hosts enterprise policy, this omission allows an extension to retrieve sensitive ad-tech user segmentation data (userBiddingSignals) and the exact URL where the user joined the group (joiningOrigin, effectively leaking browsing history) from domains it is explicitly blocked from accessing.

Potential Reproduction Steps

Note: These are suggested steps; our setup does not have the ability to execute code to verify them.

  1. An attacker creates a Chrome extension with the debugger permission.
  2. The victim installs the extension in an environment with enterprise policies blocking access to restricted-domain.com.
  3. The extension attaches its debugger to a benign, permitted tab: chrome.debugger.attach({tabId: allowedTabId}, '1.3').
  4. The extension enables interest group tracking: chrome.debugger.sendCommand({tabId: allowedTabId}, 'Storage.setInterestGroupTracking', {enable: true}).
  5. The extension listens for chrome.debugger.onEvent with the method Storage.interestGroupAccessed.
  6. The victim opens a new tab and navigates to restricted-domain.com, which calls navigator.joinAdInterestGroup().
  7. The extension receives the event containing the ownerOrigin and name from the restricted tab.
  8. The extension uses the observed ownerOrigin and name to call Storage.getInterestGroupDetails, successfully retrieving the full interest group data, including joiningOrigin and userBiddingSignals, bypassing the enterprise blocklist.

Suggested Fix

  1. Add Permission Checks: In StorageHandler::GetInterestGroupDetails, verify the requested owner_origin against client_->MayAttachToURL() before querying the database, similar to how cookies are handled.
  2. Filter Observer Events: InterestGroupManagerImpl or StorageHandler should filter interest group access events. Events should only be dispatched to DevTools clients that are attached to the RenderFrameHost or WebContents where the event originated, or at a minimum, the owner_origin of the event should be checked against MayAttachToURL() before being forwarded in StorageHandler::OnInterestGroupAccessed.

Evaluated with Chrome root at commit: bb48272cafb7e24c93f55ef40da398cd206ee651


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