CVE-2026-8004
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/devtools/protocol/storage_handler.cc |
modified |
Files Changed
content/browser/devtools/protocol/storage_handler.ccthird_party/blink/web_tests/http/tests/inspector-protocol/storage/interest-groups-expected.txt
Patch
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
Regression Test / PoC
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 {
Original Bug Report
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.cccontent/browser/interest_group/interest_group_manager_impl.cccontent/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
- Profile-Wide Observation: In
content/browser/devtools/protocol/storage_handler.cc, theSetInterestGroupTrackingInternalmethod registers theStorageHandleras an observer onInterestGroupManagerImpl. BecauseInterestGroupManagerImplis scoped to theStoragePartition(the entire profile), the observer listens for all interest group events globally. - Unfiltered Broadcasting: When an interest group event occurs in any tab,
InterestGroupManagerImpl::NotifyInterestGroupAccessediterates through all observers unconditionally. It does not filter byRenderFrameHostor tab ID. TheStorageHandler::OnInterestGroupAccessedthen forwards the event details (includingowner_originandname) to the DevTools frontend. - Missing Authorization Check: The
StorageHandler::GetInterestGroupDetailsmethod retrieves the full interest group configuration using an(owner_origin, name)pair. Unlike other sensitive methods inStorageHandler(e.g.,GetCookies, which filters results throughNetworkHandler::CanAccessCookieandclient_->MayAttachToURL()),GetInterestGroupDetailsentirely omits theMayAttachToURL()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.
- An attacker creates a Chrome extension with the
debuggerpermission. - The victim installs the extension in an environment with enterprise policies blocking access to
restricted-domain.com. - The extension attaches its debugger to a benign, permitted tab:
chrome.debugger.attach({tabId: allowedTabId}, '1.3'). - The extension enables interest group tracking:
chrome.debugger.sendCommand({tabId: allowedTabId}, 'Storage.setInterestGroupTracking', {enable: true}). - The extension listens for
chrome.debugger.onEventwith the methodStorage.interestGroupAccessed. - The victim opens a new tab and navigates to
restricted-domain.com, which callsnavigator.joinAdInterestGroup(). - The extension receives the event containing the
ownerOriginandnamefrom the restricted tab. - The extension uses the observed
ownerOriginandnameto callStorage.getInterestGroupDetails, successfully retrieving the full interest group data, includingjoiningOriginanduserBiddingSignals, bypassing the enterprise blocklist.
Suggested Fix
- Add Permission Checks: In
StorageHandler::GetInterestGroupDetails, verify the requestedowner_originagainstclient_->MayAttachToURL()before querying the database, similar to how cookies are handled. - Filter Observer Events:
InterestGroupManagerImplorStorageHandlershould filter interest group access events. Events should only be dispatched to DevTools clients that are attached to theRenderFrameHostorWebContentswhere the event originated, or at a minimum, theowner_originof the event should be checked againstMayAttachToURL()before being forwarded inStorageHandler::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.