CVE-2025-4052
Overview
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
RecorderControllerthat 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.
Attack Path
- Craft a malicious recording An attacker prepares a recording file whose contents are intended to abuse the Recorder import flow.
- 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”).
- 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. - 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
(inferred) rather than demonstrated by the patch.Changed Functions
| Function | Change | Notes |
|---|---|---|
constructorfront_end/panels/recorder/RecorderController.ts |
modified | |
iffront_end/panels/recorder/RecorderController.ts |
modified |
Files Changed
front_end/panels/recorder/BUILD.gnfront_end/panels/recorder/RecorderController.tsfront_end/ui/visual_logging/KnownContextValues.ts
Audit Directions
- Unguarded import/load entry pointsAudit 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 guardsCheck that every content-ingestion path reuses the shared
TypeToAllowDialogpattern and honors the same bypass settings (disable-self-xss-warning, testing flags) rather than each panel re-implementing or omitting the check. - Persisted-consent settingsReview
SYNCED“disable warning” settings to ensure a first-time acknowledgement cannot be silently pre-set to bypass future prompts for unrelated dangerous actions.
Patch
From a702323c7c0f226bd02c3971de19f9bbc98a4de1 Mon Sep 17 00:00:00 2001 From: Alex Rudenko <[email protected]> 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 <[email protected]> Reviewed-by: Wolfgang Beyer <[email protected]> --- 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',
Original Bug Report
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:
- Download attached HTML file.
- Download the recorder script provided in the tutorial page.
- 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