Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in DevTools
DescriptionInappropriate implementation in DevTools
ComponentDevTools
Bug ClassLogic Error
Tracker517741170
Fix commit15d1642ee596 (chromium/src) +76/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

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

Files Changed

  • content/browser/devtools/protocol/emulation_handler.cc
  • content/browser/devtools/protocol/emulation_handler.h
  • third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable-expected.txt
  • third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable.js
From 15d1642ee596c6f6d79ee080e1f0f43e5078b7b5 Mon Sep 17 00:00:00 2001
From: Alex Rudenko <[email protected]>
Date: Fri, 29 May 2026 01:10:45 -0700
Subject: [PATCH] DevTools: remove geolocation override on disable()

Fixed: 517741170
Change-Id: I6e765d63d7cf316b53fdb5488ce3e94e10100472
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7884999
Commit-Queue: Simon Zünd <[email protected]>
Commit-Queue: Alex Rudenko <[email protected]>
Auto-Submit: Alex Rudenko <[email protected]>
Reviewed-by: Simon Zünd <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1638296}
---

diff --git a/content/browser/devtools/protocol/emulation_handler.cc b/content/browser/devtools/protocol/emulation_handler.cc
index 4e75ecc0..1793497 100644
--- a/content/browser/devtools/protocol/emulation_handler.cc
+++ b/content/browser/devtools/protocol/emulation_handler.cc
@@ -194,6 +194,9 @@
   pressure_overrides_.clear();
 #endif  // BUILDFLAG(ENABLE_COMPUTE_PRESSURE)
   ClearDevicePostureOverride();
+  if (geolocation_overridden_) {
+    ClearGeolocationOverride();
+  }
   return Response::Success();
 }
 
@@ -600,6 +603,7 @@
             /*error_message=*/"", /*error_technical=*/""));
   }
   geolocation_context->SetOverride(std::move(override_result));
+  geolocation_overridden_ = true;
   return Response::Success();
 }
 
@@ -609,6 +613,7 @@
 
   auto* geolocation_context = GetWebContents()->GetGeolocationContext();
   geolocation_context->ClearOverride();
+  geolocation_overridden_ = false;
   return Response::Success();
 }
 
diff --git a/content/browser/devtools/protocol/emulation_handler.h b/content/browser/devtools/protocol/emulation_handler.h
index ea99118b..069f55b 100644
--- a/content/browser/devtools/protocol/emulation_handler.h
+++ b/content/browser/devtools/protocol/emulation_handler.h
@@ -220,6 +220,9 @@
   // True when screen orientation lock emulation is enabled.
   bool screen_orientation_lock_emulation_enabled_ = false;
 
+  // True when SetGeolocationOverride() has been called.
+  bool geolocation_overridden_ = false;
+
   raw_ptr<RenderFrameHostImpl> host_;
 
   std::unique_ptr<Emulation::Frontend> frontend_;
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable-expected.txt
new file mode 100644
index 0000000..625847c
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable-expected.txt
@@ -0,0 +1,25 @@
+Tests that geolocation override is cleared on session disconnect.
+
+Get original geolocation data
+Geolocation data: {
+    code : 3
+    message : Timeout expired
+}
+
+Set required geolocation override fields
+Geolocation data: {
+    accuracy : 1.23
+    altitude : null
+    altitudeAccuracy : null
+    heading : null
+    latitude : 56.83
+    longitude : 60.63
+    speed : null
+}
+
+Disconnect session
+Geolocation data: {
+    code : 3
+    message : Timeout expired
+}
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable.js b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable.js
new file mode 100644
index 0000000..0fbce8d
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable.js
@@ -0,0 +1,43 @@
+(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
+  const {page, session, dp} = await testRunner.startBlank(
+      'Tests that geolocation override is cleared on session disconnect.');
+
+  await dp.Browser.grantPermissions({
+    origin: location.origin,
+    permissions: ['geolocation'],
+  });
+
+  async function logGeolocationData(activeSession) {
+    const result = await activeSession.evaluateAsync(`
+      new Promise(
+        resolve => window.navigator.geolocation.getCurrentPosition(
+          position => resolve(position.coords.toJSON()),
+          error => resolve({code: error.code, message: error.message}),
+          {timeout: 200}
+      ))`);
+    testRunner.log(result, 'Geolocation data: ');
+  }
+
+  testRunner.log('\nGet original geolocation data');
+  await logGeolocationData(session);
+
+  testRunner.log('\nSet required geolocation override fields');
+  await dp.Emulation.setGeolocationOverride({
+    latitude: 56.83,
+    longitude: 60.63,
+    accuracy: 1.23,
+  });
+  await logGeolocationData(session);
+
+  testRunner.log('\nDisconnect session');
+  await session.disconnect();
+
+  const nextSession = await page.createSession();
+  await nextSession.protocol.Browser.grantPermissions({
+    origin: location.origin,
+    permissions: ['geolocation'],
+  });
+  await logGeolocationData(nextSession);
+
+  testRunner.completeTest();
+});
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable-expected.txt
new file mode 100644
index 0000000..625847c
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable-expected.txt
@@ -0,0 +1,25 @@
+Tests that geolocation override is cleared on session disconnect.
+
+Get original geolocation data
+Geolocation data: {
+    code : 3
+    message : Timeout expired
+}
+
+Set required geolocation override fields
+Geolocation data: {
+    accuracy : 1.23
+    altitude : null
+    altitudeAccuracy : null
+    heading : null
+    latitude : 56.83
+    longitude : 60.63
+    speed : null
+}
+
+Disconnect session
+Geolocation data: {
+    code : 3
+    message : Timeout expired
+}
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable.js b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable.js
new file mode 100644
index 0000000..0fbce8d
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-geolocation-override-disable.js
@@ -0,0 +1,43 @@
+(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
+  const {page, session, dp} = await testRunner.startBlank(
+      'Tests that geolocation override is cleared on session disconnect.');
+
+  await dp.Browser.grantPermissions({
+    origin: location.origin,
+    permissions: ['geolocation'],
+  });
+
+  async function logGeolocationData(activeSession) {
+    const result = await activeSession.evaluateAsync(`
+      new Promise(
+        resolve => window.navigator.geolocation.getCurrentPosition(
+          position => resolve(position.coords.toJSON()),
+          error => resolve({code: error.code, message: error.message}),
+          {timeout: 200}
+      ))`);
+    testRunner.log(result, 'Geolocation data: ');
+  }
+
+  testRunner.log('\nGet original geolocation data');
+  await logGeolocationData(session);
+
+  testRunner.log('\nSet required geolocation override fields');
+  await dp.Emulation.setGeolocationOverride({
+    latitude: 56.83,
+    longitude: 60.63,
+    accuracy: 1.23,
+  });
+  await logGeolocationData(session);
+
+  testRunner.log('\nDisconnect session');
+  await session.disconnect();
+
+  const nextSession = await page.createSession();
+  await nextSession.protocol.Browser.grantPermissions({
+    origin: location.origin,
+    permissions: ['geolocation'],
+  });
+  await logGeolocationData(nextSession);
+
+  testRunner.completeTest();
+});
Loading diff…

Original Bug Report

reported by [email protected]

Persistence of DevTools Geolocation override after debugger detachment

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: When a DevTools session detaches, EmulationHandler::Disable() fails to clear the active geolocation override. Because the geolocation context is tab-scoped and managed at the WebContents level, this spoofed position potentially persists across cross-origin navigations within the same tab after the debugger is closed. This could allow stealth geolocation spoofing by an extension using the debugger API without displaying the debugging infobar.

Affected files:

  • content/browser/devtools/protocol/emulation_handler.cc

Estimated timestamp from git blame: 2015-03-19

Root Cause Analysis

When a DevTools session detaches or is closed, the browser invokes EmulationHandler::Disable() (defined in content/browser/devtools/protocol/emulation_handler.cc, line 173) to clean up domain-specific overrides. While it successfully calls cleanup methods for touch, device, focus, and device posture (ClearDevicePostureOverride()), it does not call ClearGeolocationOverride() (defined on line 606).

Unlike other overrides tied directly to a RenderFrameHost (which are naturally replaced upon navigation), the geolocation override is stored on a tab-scoped context. Specifically, SetGeolocationOverride writes to device::mojom::GeolocationContext via GetWebContents()->GetGeolocationContext().

WebContentsImpl::geolocation_context_ (defined in content/browser/web_contents/web_contents_impl.h, line 2549) is bound once per tab and persists across navigations. When a new navigation occurs within the same tab, a new frame-specific GeolocationImpl binding is created on the device-service side, which inherits the existing geoposition_override_ state (defined in services/device/geolocation/geolocation_context.h, line 56). Because the cleanup method is omitted when the DevTools session is disabled, the spoofed position persists silently across navigations after the debugger detaches and the user-facing debugger infobar is removed.

Potential Impact

This leads to cross-origin geolocation spoofing that outlives the active DevTools UI indicator. A browser extension with debugger capabilities can attach to a target tab, apply a mock geolocation, detach itself (which removes the debugger infobar), and disappear from the UI. If the user subsequently navigates that tab to a site holding geolocation permissions (e.g., https://maps.google.com), the site will receive the attacker’s mock coordinates instead of the actual physical position of the device. This override remains active for all origins visited in that tab until the tab itself is closed.

Suggested / Potential Steps to Trigger the Bug

Note: These steps are suggested/potential as we currently do not have the ability to run code or execute a live proof of concept.

  1. Install an extension containing the debugger permission.
  2. Programmatically attach to an active target tab:
    chrome.debugger.attach({tabId}, '1.3')
    
  3. Apply a mock geolocation override:
    chrome.debugger.sendCommand({tabId}, 'Emulation.setGeolocationOverride', {latitude: 37.0, longitude: -122.0, accuracy: 1})
    
  4. Programmatically detach from the session:
    chrome.debugger.detach({tabId})
    
    Observe that the “<extension> is debugging this browser” infobar disappears from the browser UI.
  5. Navigate that same tab to a target origin holding active geolocation permission.
  6. The website’s call to navigator.geolocation.getCurrentPosition() will receive the spoofed coordinates (latitude 37.0, longitude -122.0) silently, with no indication that an emulation is active.

Suggested Fix

In content/browser/devtools/protocol/emulation_handler.cc, update EmulationHandler::Disable() to explicitly clear the geolocation override when the DevTools session is disabled or closed:

Response EmulationHandler::Disable() {
  ...
  ClearDevicePostureOverride();
  ClearGeolocationOverride(); // Reset active geolocation overrides
  return Response::Success();
}

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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