Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in DevTools
DescriptionInsufficient validation of untrusted input in DevTools
ComponentDevTools
Bug ClassLogic Error
Tracker497255035
Fix commita77ddcdbea95 (chromium/src) +8/-12
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

Files Changed

  • content/browser/devtools/devtools_agent_host_impl.cc
  • content/browser/renderer_host/render_frame_host_impl.cc
From a77ddcdbea95bd88b0534a0d33fcc7f2e30ab4a2 Mon Sep 17 00:00:00 2001
From: Danil Somsikov <[email protected]>
Date: Thu, 02 Apr 2026 05:00:50 -0700
Subject: [PATCH] Fix DevTools Target Confusion vulnerability via Fenced Frames

A compromised renderer can hijack DevTools sessions for cross-origin
pages by supplying a stolen 'devtools_frame_token' during fenced frame
creation. Previously, the browser improperly scoped token uniqueness
validation to the current tab and silently overwrote the process-global
DevTools target map on collision.

This CL mitigates the vulnerability by:

1. Updating the validation in to check for global uniqueness across all
DevTools instances using `DevToolsAgentHost::GetForId()`. If a collision
is detected, the browser will now treat it as a Bad Message and
terminate the malicious renderer process.

2. Hardening the map insertion logic in
`DevToolsAgentHostImpl::NotifyCreated` by upgrading a `DCHECK` to a
`CHECK`. This ensures that even if a collision somehow bypasses initial
validation, the browser will crash safely in production rather than
allowing a silent target hijack.

Bug: 497255035
Change-Id: I9857e1378ab289be626e71aa12944ef8c9e9cb2f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7716666
Auto-Submit: Danil Somsikov <[email protected]>
Reviewed-by: Andrey Kosyakov <[email protected]>
Reviewed-by: Arthur Sonzogni <[email protected]>
Commit-Queue: Danil Somsikov <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1609111}
---

diff --git a/content/browser/devtools/devtools_agent_host_impl.cc b/content/browser/devtools/devtools_agent_host_impl.cc
index e47209e..d7c727ed 100644
--- a/content/browser/devtools/devtools_agent_host_impl.cc
+++ b/content/browser/devtools/devtools_agent_host_impl.cc
@@ -556,7 +556,7 @@
 }
 
 void DevToolsAgentHostImpl::NotifyCreated() {
-  DCHECK(!GetDevtoolsInstances().contains(id_));
+  CHECK(!GetDevtoolsInstances().contains(id_));
   GetDevtoolsInstances()[id_] = this;
   for (auto& observer : GetDevtoolsObservers())
     observer.DevToolsAgentHostCreated(this);
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 3f5964a6..cb6aa69 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -200,6 +200,7 @@
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/context_menu_params.h"
 #include "content/public/browser/cookie_access_details.h"
+#include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/disallow_activation_reason.h"
 #include "content/public/browser/document_ref.h"
 #include "content/public/browser/document_service_internal.h"
@@ -10586,17 +10587,12 @@
     return;
   }
 
-  // Ensure the devtools frame token doesn't exist in the FrameTree for
-  // this tab.
-  for (FrameTreeNode* node :
-       GetOutermostMainFrame()->frame_tree()->NodesIncludingInnerTreeNodes()) {
-    if (node->current_frame_host()->devtools_frame_token() ==
-        devtools_frame_token) {
-      bad_message::ReceivedBadMessage(
-          GetProcess(),
-          bad_message::RFHI_CREATE_FENCED_FRAME_BAD_DEVTOOLS_FRAME_TOKEN);
-      return;
-    }
+  // Ensure the devtools frame token doesn't exist globally.
+  if (DevToolsAgentHost::GetForId(devtools_frame_token.ToString())) {
+    bad_message::ReceivedBadMessage(
+        GetProcess(),
+        bad_message::RFHI_CREATE_FENCED_FRAME_BAD_DEVTOOLS_FRAME_TOKEN);
+    return;
   }
 
   // Inactive pages cannot create fenced frames. If the page is in the BFCache,
Loading diff…

Original Bug Report

reported by [email protected]

DevTools Target Confusion via Fenced Frame Token Collision

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

Overview: A compromised renderer can hijack DevTools sessions for cross-origin pages by supplying a stolen devtools_frame_token during fenced frame creation. The browser improperly scopes token uniqueness validation to the current tab and silently overwrites the process-global DevTools target map on collision, allowing an attacker to intercept Chrome DevTools Protocol (CDP) commands.

Affected files:

  • content/browser/devtools/devtools_agent_host_impl.cc
  • content/browser/devtools/render_frame_devtools_agent_host.cc
  • content/browser/renderer_host/render_frame_host_impl.cc
  • content/browser/renderer_host/render_frame_proxy_host.cc

Estimated timestamp from git blame: 2026-01-06

Overview

A logic error exists in DevToolsAgentHostImpl where process-global DevTools target IDs can be overwritten due to insufficient validation during Fenced Frame creation. A compromised renderer can exploit this to hijack DevTools automation sessions (e.g., Puppeteer) connecting to cross-origin targets, leading to cross-origin data theft.

Vulnerability Details

The issue stems from a combination of three factors:

  1. Cross-WebContents Token Leak: When a renderer opens a cross-origin popup, the browser sends a PageBroadcast.CreateRemoteMainFrame Mojo IPC to the opener renderer so it can build a remote frame proxy. This IPC leaks the victim frame’s devtools_frame_token to the potentially compromised opener renderer.
  2. Insufficient Validation Scope: When a renderer creates a Fenced Frame, it supplies a devtools_frame_token via the LocalFrameHost.CreateFencedFrame Mojo IPC. RenderFrameHostImpl::CreateFencedFrame attempts to ensure this token is unique to prevent collisions. However, it only checks nodes within the current tab’s FrameTree (GetOutermostMainFrame()->frame_tree()->NodesIncludingInnerTreeNodes()). It fails to check for collisions across different WebContents.
  3. Silent Map Overwrite: When the DevTools agent host is created for the new fenced frame, DevToolsAgentHostImpl::NotifyCreated registers it in a process-global map:
    void DevToolsAgentHostImpl::NotifyCreated() {
      DCHECK(!GetDevtoolsInstances().contains(id_));
      GetDevtoolsInstances()[id_] = this;
      // ...
    }
    
    Because DCHECK is compiled out in Release builds, the global map entry for the victim’s token is silently overwritten with a pointer to the attacker’s new fenced frame agent host.

Potential Attack Steps

Note: These are theoretical steps as our tooling does not yet have the ability to run code to produce a working proof-of-concept.

  1. An attacker gains code execution in a renderer process (e.g., via a V8 bug).
  2. The compromised renderer opens a cross-origin window to a sensitive site (e.g., window.open('https://victim.com')).
  3. The browser sends a CreateRemoteMainFrame IPC to the attacker’s renderer. The attacker intercepts this IPC and extracts the victim’s devtools_frame_token.
  4. The compromised renderer crafts a LocalFrameHost.CreateFencedFrame Mojo IPC to create a fenced frame in its own document, passing the stolen victim’s devtools_frame_token instead of a randomly generated one.
  5. The browser’s validation passes because the victim is in a different WebContents.
  6. The browser creates a RenderFrameDevToolsAgentHost for the attacker’s fenced frame, which silently overwrites the victim’s entry in the global GetDevtoolsInstances() map.
  7. An automated DevTools client (like Puppeteer or a Chrome Extension) attempts to attach to the victim’s target ID.
  8. DevToolsAgentHostImpl::GetForId returns the attacker’s fenced frame. The DevTools client unknowingly sends CDP commands (e.g., Runtime.evaluate to inject scripts or Input.dispatchKeyEvent to type passwords) into the attacker’s frame, allowing the attacker to steal credentials or hijack the session.

Suggested Fix

  1. Harden Map Insertion: In DevToolsAgentHostImpl::NotifyCreated(), upgrade the DCHECK to a CHECK, or safely handle the collision without overwriting existing valid instances.
  2. Enforce Global Uniqueness: Update the validation in RenderFrameHostImpl::CreateFencedFrame to check the global DevToolsAgentHost::GetForId() (or a truly global registry) to ensure the token doesn’t collide with any existing target across all WebContents.
  3. Browser-Side Token Generation: Consider having the browser unconditionally generate the devtools_frame_token for Fenced Frames rather than trusting the token provided by the renderer via IPC.

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from 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. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker
Links in the report