Low watch CVSS 9.8 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
9.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in DevTools
DescriptionInappropriate implementation in DevTools
ComponentDevTools
Sub-componentRecorder
Bug ClassLogic Error
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.
Tracker401927528
Fix commita702323c7c0f (devtools/devtools-frontend) +68/-4
CWECWE-838
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
Creditedvanillawebdev
Reported2025-03-10 · bounty $1000 (per vendor advisory)
Fix landed2025-03-14 (public git)
Disclosed2025-04-29
Patch gap46 days — fix public in source before the advisory

Background

DevTools Recorder
A DevTools panel that records and replays user interaction flows and can import a recording from a file supplied by the user.
Self-XSS confirmation pattern
A DevTools protection requiring the user to type a confirmation phrase before running/pasting untrusted content, guarding against social-engineering (“paste this into DevTools”) attacks.
`PanelCommon.TypeToAllowDialog`
A shared dialog that forces the user to type an exact phrase (here allow importing) before an action proceeds.
`#acknowledgeImportNotice()` / `#onImportRecording()`
The new gatekeeper method and the import event handler in RecorderController that together decide whether the file selector for importing a recording is shown.

Root Cause Analysis

Before the patch, #onImportRecording() immediately created and clicked a file selector (UI.UIUtils.createFileSelectorElement(...).click()), letting a user import a recording with no warning about the risk of importing untrusted content. Because a recording is executable-flow content, an attacker could socially engineer a victim into importing a malicious recording, with no confirmation step — the same self-XSS class the Recorder had not been protecting against.

The fix converts #onImportRecording() to async and gates the file selector behind await this.#acknowledgeImportNotice(), which shows a TypeToAllowDialog requiring the user to type “allow importing” unless a bypass condition holds (#disableRecorderImportWarningSetting, testing/isChromeForTesting, disableSelfXssWarnings, or #selfXssWarningDisabledSetting). On success it remembers consent via #disableRecorderImportWarningSetting.set(true) so the prompt shows only the first time. This tightens the flow by inserting an explicit human-in-the-loop acknowledgement before the potentially dangerous import proceeds.

Key insight
The core mistake was allowing recording import to proceed with no risk acknowledgement, exposing users to self-XSS-style social engineering; the fix requires a type-to-confirm dialog (the same pattern used for pasting) before the import file selector is shown.

Attack Path

  1. Craft a malicious recording An attacker prepares a recording file whose contents are intended to abuse the Recorder import flow.
  2. Social-engineer the victim The attacker convinces the user to import that recording into DevTools Recorder (e.g. “load this recording to reproduce the bug”).
  3. Pre-patch: silent import #onImportRecording() directly opens the file selector and imports the chosen file with no warning, so the untrusted recording is loaded without informed consent.
  4. Consequence The imported recording is processed by the Recorder. (inferred) The advisory describes this as an inappropriate implementation; any concrete code execution or identity theft is the risk the new warning text cites, not a step demonstrated by the diff.

Impact Assessment

The weakness lets an attacker rely on social engineering to get a victim to import an untrusted recording into DevTools Recorder without any confirmation, within the privileged DevTools front-end context; the precondition is convincing the user to perform the import. The advisory classifies it as an inappropriate implementation (logic error) in DevTools and does not report in-the-wild exploitation; the warning’s cited outcomes (identity theft, taking control of the computer) are the risks being mitigated and are (inferred) rather than demonstrated by the patch.

Changed Functions

FunctionChangeNotes
constructor
front_end/panels/recorder/RecorderController.ts
modified
if
front_end/panels/recorder/RecorderController.ts
modified

Files Changed

  • front_end/panels/recorder/BUILD.gn
  • front_end/panels/recorder/RecorderController.ts
  • front_end/ui/visual_logging/KnownContextValues.ts

Audit Directions

  • Unguarded import/load entry points
    Audit other DevTools panels that import, load, or paste user-supplied content for missing type-to-confirm / self-XSS protection before the content is accepted.
  • Consistency of self-XSS guards
    Check that every content-ingestion path reuses the shared TypeToAllowDialog pattern and honors the same bypass settings (disable-self-xss-warning, testing flags) rather than each panel re-implementing or omitting the check.
  • Persisted-consent settings
    Review SYNCED “disable warning” settings to ensure a first-time acknowledgement cannot be silently pre-set to bypass future prompts for unrelated dangerous actions.
From a702323c7c0f226bd02c3971de19f9bbc98a4de1 Mon Sep 17 00:00:00 2001
From: Alex Rudenko <alexrudenko@chromium.org>
Date: Fri, 14 Mar 2025 09:15:20 +0100
Subject: [PATCH] [Recorder] Confirm recording import the first time

Uses the same pattern that is used to confirm pasting.

Fixed: 402071098
Bug: 401927528
Change-Id: I1317fa81bff3cb5a17e4923596e10af05350fb82
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6343138
Commit-Queue: Alex Rudenko <alexrudenko@chromium.org>
Reviewed-by: Wolfgang Beyer <wolfi@chromium.org>
---

diff --git a/front_end/panels/recorder/BUILD.gn b/front_end/panels/recorder/BUILD.gn
index bcb4e17..765fd2d 100644
--- a/front_end/panels/recorder/BUILD.gn
+++ b/front_end/panels/recorder/BUILD.gn
@@ -21,8 +21,10 @@
   deps = [
     "../../core/common:bundle",
     "../../core/platform:bundle",
+    "../../core/root:bundle",
     "../../models/extensions:bundle",
     "../../models/trace:bundle",
+    "../../panels/common:bundle",
     "../../panels/emulation:bundle",
     "../../panels/timeline:bundle",
     "../../services/tracing:bundle",
diff --git a/front_end/panels/recorder/RecorderController.ts b/front_end/panels/recorder/RecorderController.ts
index 662270b..3909bf9 100644
--- a/front_end/panels/recorder/RecorderController.ts
+++ b/front_end/panels/recorder/RecorderController.ts
@@ -6,10 +6,12 @@
 import * as Host from '../../core/host/host.js';
 import * as i18n from '../../core/i18n/i18n.js';
 import * as Platform from '../../core/platform/platform.js';
+import * as Root from '../../core/root/root.js';
 import * as SDK from '../../core/sdk/sdk.js';
 import * as Bindings from '../../models/bindings/bindings.js';
 import * as PublicExtensions from '../../models/extensions/extensions.js';
 import type * as Trace from '../../models/trace/trace.js';
+import * as PanelCommon from '../../panels/common/common.js';
 import * as Emulation from '../../panels/emulation/emulation.js';
 import * as Timeline from '../../panels/timeline/timeline.js';
 import * as Tracing from '../../services/tracing/tracing.js';
@@ -119,7 +121,27 @@
   /**
    * @description Link text to forward to a documentation page on the recorder.
    */
-  learnMore: 'Learn more'
+  learnMore: 'Learn more',
+  /**
+   *@description Headline of warning shown to users when users import a recording into DevTools Recorder.
+   */
+  doYouTrustThisCode: 'Do you trust this recording?',
+  /**
+   *@description Warning shown to users when imports code into DevTools Recorder.
+   *@example {allow importing} PH1
+   */
+  doNotImport:
+      'Don\'t import recordings you do not understand or have not reviewed yourself into DevTools. This could allow attackers to steal your identity or take control of your computer. Please type \'\'{PH1}\'\' below to allow importing.',
+  /**
+   *@description Text a user needs to type in order to confirm that they
+   *are aware of the danger of import code into the DevTools Recorder.
+   */
+  allowImporting: 'allow importing',
+  /**
+   *@description Input box placeholder which instructs the user to type 'allow pasing' into the input box.
+   *@example {allow importing} PH1
+   */
+  typeAllowImporting: 'Type \'\'{PH1}\'\'',
 } as const;
 const str_ = i18n.i18n.registerUIStrings('panels/recorder/RecorderController.ts', UIStrings);
 const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -224,6 +246,11 @@
   #recorderSettings = new Models.RecorderSettings.RecorderSettings();
   #shortcutHelper = new Models.RecorderShortcutHelper.RecorderShortcutHelper();
 
+  #disableRecorderImportWarningSetting = Common.Settings.Settings.instance().createSetting(
+      'disable-recorder-import-warning', false, Common.Settings.SettingStorageType.SYNCED);
+  #selfXssWarningDisabledSetting = Common.Settings.Settings.instance().createSetting(
+      'disable-self-xss-warning', false, Common.Settings.SettingStorageType.SYNCED);
+
   constructor() {
     super();
 
@@ -967,11 +994,43 @@
         ?.click();
   }
 
-  #onImportRecording(event: Event): void {
+  async #acknowledgeImportNotice(): Promise<boolean> {
+    if (this.#disableRecorderImportWarningSetting.get()) {
+      return true;
+    }
+
+    if (Root.Runtime.Runtime.queryParam('isChromeForTesting') ||
+        Root.Runtime.Runtime.queryParam('disableSelfXssWarnings') || this.#selfXssWarningDisabledSetting.get()) {
+      return true;
+    }
+
+    const result = await PanelCommon.TypeToAllowDialog.show({
+      jslogContext: {
+        input: 'confirm-import-recording-input',
+        dialog: 'confirm-import-recording-dialog',
+      },
+      message: i18nString(UIStrings.doNotImport, {PH1: i18nString(UIStrings.allowImporting)}),
+      header: i18nString(UIStrings.doYouTrustThisCode),
+      typePhrase: i18nString(UIStrings.allowImporting),
+      inputPlaceholder: i18nString(UIStrings.typeAllowImporting, {PH1: i18nString(UIStrings.allowImporting)}),
+    });
+
+    if (result) {
+      this.#disableRecorderImportWarningSetting.set(true);
+    }
+
+    return result;
+  }
+
+  async #onImportRecording(event: Event): Promise<void> {
     event.stopPropagation();
+
     this.#clearError();
-    this.#fileSelector = UI.UIUtils.createFileSelectorElement(this.#importFile.bind(this));
-    this.#fileSelector.click();
+
+    if (await this.#acknowledgeImportNotice()) {
+      this.#fileSelector = UI.UIUtils.createFileSelectorElement(this.#importFile.bind(this));
+      this.#fileSelector.click();
+    }
   }
 
   async #onPlayRecordingByName(event: Components.RecordingListView.PlayRecordingEvent): Promise<void> {
diff --git a/front_end/ui/visual_logging/KnownContextValues.ts b/front_end/ui/visual_logging/KnownContextValues.ts
index 17642a6..010ea25 100644
--- a/front_end/ui/visual_logging/KnownContextValues.ts
+++ b/front_end/ui/visual_logging/KnownContextValues.ts
@@ -791,6 +791,8 @@
   'conditional-breakpoint',
   'configure',
   'confirm',
+  'confirm-import-recording-dialog',
+  'confirm-import-recording-input',
   'connection',
   'connection-id',
   'consent-onboarding',
@@ -1196,6 +1198,7 @@
   'disable-file-breakpoints',
   'disable-locale-info-bar',
   'disable-paused-state-overlay',
+  'disable-recorder-import-warning',
   'disable-self-xss-warning',
   'disabled',
   'disallowed-select-descendants-details',
Loading diff…

Original Bug Report

reported by bi...@gmail.com

DevTools Recorder Can Flip Internal Flags Without User Awareness

Security Bug

Important: Please do not change the component of this bug manually.

Please READ THIS FAQ before filing a bug: https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/faq.md

Please see the following link for instructions on filing security bugs: https://www.chromium.org/Home/chromium-security/reporting-security-bugs

Reports may be eligible for reward payments under the Chrome VRP: https://g.co/chrome/vrp

NOTE: Security bugs are normally made public once a fix has been widely deployed.


VULNERABILITY DETAILS

Chrome DevTools Recorder currently allows regular users to automate steps on chrome:// URLs. This opens the door for social engineering attacks, where users can be tricked into executing pre-recorded scripts that flips flags without clear user consent.

This issue also bleeds to ChromeOS where extensions requires explicit permission to allow injecting script in chrome://file-manager/* URLs, which is currently disabled by default. By flipping a certain flag (#extensions-on-chrome-urls), the attacker could start another system attack.

VERSION

Tested in: Chrome Version: 134.0.6998.36 (Official Build) (64-bit) Operating System: Windows NT: 10.0.19044

REPRODUCTION CASE

I’ve attached an HTML page that looks like a valid tutorial on enabling 60FPS for web mobile games. It guides users to download and run a DevTools Recorder script that flips #extensions-on-chrome-urls flag without their full awareness. Follow the guide in the HTML file:

  1. Download attached HTML file.
  2. Download the recorder script provided in the tutorial page.
  3. Import the and replay the recorder file in DevTools Recorder.

Since the chrome://flags can automatically jump to certain flags, this makes the victim unaware that the target flag has been flipped. Furthermore, the warning at the very top of the page is not visible upon Relaunch as seen in the video

In this particular example, the flag is used to allows chrome extensions to run in chrome:// URLs.

SUGGESTED SOLUTION

To perform recorder steps in chrome://* URLs, the user should explicitly enable a flag in chrome://flags.

CREDIT INFORMATION

Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited? Reporter credit: vanillawebdev

View on issue tracker