CVE-2026-79176
Overview
Background
- `chrome.fileSystem.chooseEntry`
- A platform-app/extension API that opens a native file picker to select files or directories, parameterized by a
ChooseEntryType(openFile,openWritableFile,openDirectory,saveFile). - `suggestedName`
- An optional caller-supplied default file name that the API appends to the picker’s initial path.
- `picker_type` / `SELECT_SAVEAS_FILE`
- The
ui::SelectFileDialogpicker kind;SELECT_SAVEAS_FILEis the “save as” dialog, distinct from open-file and open-directory pickers. - `file_type_info` / accept filter
- The accept-type filter built from
suggested_extensionandoptions.accepts, independent of the pre-filled file name.
Root Cause Analysis
chrome.fileSystem.chooseEntry honored suggestedName for every picker type, appending it to the initial path handed to the native file dialog. For “open” and “directory” prompts—where the user selects an existing entry rather than naming a new one—this pre-filled the filename input, letting a caller pre-select or steer the user toward a specific entry that the app should not be able to name on the user’s behalf.
The fix adds a guard in the chooseEntry handler that, after building file_type_info, clears suggested_name whenever picker_type != ui::SelectFileDialog::SELECT_SAVEAS_FILE, so only save dialogs pre-fill a name. The suggested extension is still applied to the accept-type filter, so accept handling is unchanged; only the pre-selected filename behavior for non-save pickers is removed.
suggestedName to all picker types instead of only save dialogs, letting open/directory pickers arrive with a pre-filled filename; the fix clears suggested_name unless picker_type == SELECT_SAVEAS_FILE.Attack Path
- Malicious/abusive app calls chooseEntry
A platform app invokes
chrome.fileSystem.chooseEntrywith anopenFile/openDirectory/openWritableFiletype and a craftedsuggestedName. - Picker opens pre-filled
Because pre-patch code appended
suggestedNameregardless ofpicker_type, the native open/directory dialog opens with the filename input already populated with the attacker-chosen name. - User misled into selecting an entry The pre-filled name misrepresents the dialog as if a specific existing entry is the natural/default choice, nudging the user to open or grant access to that entry rather than one they intended.
- Access obtained under false pretenses
The app receives an entry the user was steered toward, achieving UI misrepresentation of the file chooser (spoofing of the intended default). The PoC confirms an
openDirectorypicker previously derived the entry fromsuggestedName, which the fix stops.
Impact Assessment
(inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
ifextensions/browser/api/file_system/file_system_api.cc |
modified | |
IN_PROC_BROWSER_TEST_Fchrome/browser/extensions/api/file_system/file_system_apitest.cc |
modified |
Files Changed
chrome/browser/extensions/api/file_system/file_system_apitest.ccextensions/browser/api/file_system/file_system_api.ccextensions/common/api/file_system.idlchrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.jschrome/browser/extensions/api/file_system/file_system_apitest_chromeos.ccchrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.jschrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.htmlchrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json
Audit Directions
- Caller-supplied defaults in native dialogsReview every parameter an extension/app can pass into
ui::SelectFileDialog(initial path, filename, filter) for whether it should be honored per picker type rather than universally. - Picker-type conditioningSearch for file/dialog options applied without branching on
SELECT_SAVEAS_FILEvs. open/directory modes, where a save-only affordance may leak into open flows. - Spoofable default selectionAudit UI flows where an app-provided string pre-selects or pre-fills a user choice, enabling misrepresentation of the user’s intended action.
Patch
From 8fdebd5b1fb9c1dd6f9dc23579d498262e9d9ca1 Mon Sep 17 00:00:00 2001
From: Giovanni Pezzino <giovax@google.com>
Date: Wed, 01 Jul 2026 11:23:13 -0700
Subject: [PATCH] fileSystem: Ignore suggestedName for non-save chooseEntry
chrome.fileSystem.chooseEntry honored suggestedName for all picker
types, appending it to the initial path passed to the native file
dialog. This pre-filled the filename input even for "open" prompts where
the user is selecting an existing entry rather than naming a new one.
Restrict the suggested name to type 'saveFile' so that open and
directory pickers start at the default directory without a pre-selected
filename. The suggested extension is still applied to the accept-type
filter so existing accept handling is unchanged.
Update the Drive open-multiple test to pick the test file explicitly now
that the open picker no longer derives it from suggestedName, and add
coverage that the open picker's initial path does not include the
suggested name.
TAG=agy
Bug: 497538341
Change-Id: I8154088fedc16e919f471c54be54157e3ff3703b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8021142
Reviewed-by: Cassy Chun-Crogan <cassycc@google.com>
Commit-Queue: Giovanni Pezzino <giovax@google.com>
Reviewed-by: Andrew Rayskiy <greengrape@google.com>
Reviewed-by: Toni Barzic <tbarzic@chromium.org>
Reviewed-by: Bo Majewski <majewski@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1655642}
---
diff --git a/extensions/browser/api/file_system/file_system_api.cc b/extensions/browser/api/file_system/file_system_api.cc
index 1cfe10e9..6deca90 100644
--- a/extensions/browser/api/file_system/file_system_api.cc
+++ b/extensions/browser/api/file_system/file_system_api.cc
@@ -766,6 +766,10 @@
BuildFileTypeInfo(&file_type_info, suggested_extension, options.accepts,
options.accepts_all_types);
+
+ if (picker_type != ui::SelectFileDialog::SELECT_SAVEAS_FILE) {
+ suggested_name.clear();
+ }
}
file_type_info.allowed_paths = ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
diff --git a/extensions/common/api/file_system.idl b/extensions/common/api/file_system.idl
index a9b5bdd..5ba68ece 100644
--- a/extensions/common/api/file_system.idl
+++ b/extensions/common/api/file_system.idl
@@ -57,7 +57,7 @@
ChooseEntryType? type;
// The suggested file name that will be presented to the user as the
- // default name to read or write. This is optional.
+ // default name to save. This is optional and only applies to 'saveFile'.
DOMString? suggestedName;
// The optional list of accept options for this file opener. Each option
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest.cc b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
index 5dbf7569..abf2a0f 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
@@ -303,6 +303,27 @@
CheckStoredDirectoryMatches(test_file);
}
+IN_PROC_BROWSER_TEST_F(FileSystemApiTest,
+ FileSystemApiOpenSuggestedNameIgnoredTest) {
+ base::FilePath test_dir = TempFilePath("sub_dir", true);
+ ASSERT_FALSE(test_dir.empty());
+ {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ ASSERT_TRUE(base::DeleteFile(test_dir));
+ ASSERT_TRUE(base::CreateDirectory(test_dir));
+ ASSERT_TRUE(base::PathService::OverrideAndCreateIfNeeded(
+ chrome::DIR_USER_DOCUMENTS, test_dir.DirName(), false, false));
+ }
+ const FileSystemChooseEntryFunction::TestOptions test_options{
+ .use_suggested_path = true};
+ auto reset_options =
+ FileSystemChooseEntryFunction::SetOptionsForTesting(test_options);
+ ASSERT_TRUE(
+ RunExtensionTest("api_test/file_system/open_suggested_name_ignored",
+ {.launch_as_platform_app = true}))
+ << message_;
+}
+
IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiOpenMultipleSuggested) {
base::FilePath test_file = TempFilePath("open_existing.txt", true);
ASSERT_FALSE(test_file.empty());
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
index 582a53f7..eba11e8 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
@@ -328,7 +328,7 @@
ASSERT_TRUE(base::PathService::OverrideAndCreateIfNeeded(
chrome::DIR_USER_DOCUMENTS, test_file.DirName(), true, false));
const FileSystemChooseEntryFunction::TestOptions test_options{
- .use_suggested_path = true};
+ .path_to_be_picked = &test_file};
auto reset_options =
FileSystemChooseEntryFunction::SetOptionsForTesting(test_options);
ASSERT_TRUE(
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js
new file mode 100644
index 0000000..0245e2d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js
@@ -0,0 +1,7 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.app.runtime.onLaunched.addListener(function() {
+ chrome.app.window.create('test.html');
+});
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json
new file mode 100644
index 0000000..ee5c1d8c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json
@@ -0,0 +1,16 @@
+{
+ "name": "chrome.fileSystem open suggested name ignored",
+ "manifest_version": 2,
+ "version": "0.1",
+ "description": "Test for chrome.fileSystem.chooseEntry ignoring suggestedName when type is openFile.",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "permissions": [
+ {
+ "fileSystem": ["directory", "write"]
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html
new file mode 100644
index 0000000..8d7d1db
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html
@@ -0,0 +1,3 @@
+<html>
+<script src="test.js"></script>
+</html>
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js
new file mode 100644
index 0000000..ceca4e19
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js
@@ -0,0 +1,22 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.test.runTests([
+ function openDirectorySuggestedNameIgnored() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory', suggestedName: 'sub_dir'},
+ chrome.test.callbackPass(function(entry) {
+ chrome.test.assertNe('sub_dir', entry.name);
+ }),
+ );
+ },
+ function saveFileSuggestedNameHonored() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'saveFile', suggestedName: 'new_file.txt'},
+ chrome.test.callbackPass(function(entry) {
+ chrome.test.assertEq('new_file.txt', entry.name);
+ }),
+ );
+ },
+]);
Regression Test / PoC
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest.cc b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
index 5dbf7569..abf2a0f 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
@@ -303,6 +303,27 @@
CheckStoredDirectoryMatches(test_file);
}
+IN_PROC_BROWSER_TEST_F(FileSystemApiTest,
+ FileSystemApiOpenSuggestedNameIgnoredTest) {
+ base::FilePath test_dir = TempFilePath("sub_dir", true);
+ ASSERT_FALSE(test_dir.empty());
+ {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ ASSERT_TRUE(base::DeleteFile(test_dir));
+ ASSERT_TRUE(base::CreateDirectory(test_dir));
+ ASSERT_TRUE(base::PathService::OverrideAndCreateIfNeeded(
+ chrome::DIR_USER_DOCUMENTS, test_dir.DirName(), false, false));
+ }
+ const FileSystemChooseEntryFunction::TestOptions test_options{
+ .use_suggested_path = true};
+ auto reset_options =
+ FileSystemChooseEntryFunction::SetOptionsForTesting(test_options);
+ ASSERT_TRUE(
+ RunExtensionTest("api_test/file_system/open_suggested_name_ignored",
+ {.launch_as_platform_app = true}))
+ << message_;
+}
+
IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiOpenMultipleSuggested) {
base::FilePath test_file = TempFilePath("open_existing.txt", true);
ASSERT_FALSE(test_file.empty());
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
index 582a53f7..eba11e8 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
@@ -328,7 +328,7 @@
ASSERT_TRUE(base::PathService::OverrideAndCreateIfNeeded(
chrome::DIR_USER_DOCUMENTS, test_file.DirName(), true, false));
const FileSystemChooseEntryFunction::TestOptions test_options{
- .use_suggested_path = true};
+ .path_to_be_picked = &test_file};
auto reset_options =
FileSystemChooseEntryFunction::SetOptionsForTesting(test_options);
ASSERT_TRUE(
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js
new file mode 100644
index 0000000..0245e2d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js
@@ -0,0 +1,7 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.app.runtime.onLaunched.addListener(function() {
+ chrome.app.window.create('test.html');
+});
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json
new file mode 100644
index 0000000..ee5c1d8c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json
@@ -0,0 +1,16 @@
+{
+ "name": "chrome.fileSystem open suggested name ignored",
+ "manifest_version": 2,
+ "version": "0.1",
+ "description": "Test for chrome.fileSystem.chooseEntry ignoring suggestedName when type is openFile.",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "permissions": [
+ {
+ "fileSystem": ["directory", "write"]
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html
new file mode 100644
index 0000000..8d7d1db
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html
@@ -0,0 +1,3 @@
+<html>
+<script src="test.js"></script>
+</html>
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js
new file mode 100644
index 0000000..ceca4e19
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js
@@ -0,0 +1,22 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.test.runTests([
+ function openDirectorySuggestedNameIgnored() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory', suggestedName: 'sub_dir'},
+ chrome.test.callbackPass(function(entry) {
+ chrome.test.assertNe('sub_dir', entry.name);
+ }),
+ );
+ },
+ function saveFileSuggestedNameHonored() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'saveFile', suggestedName: 'new_file.txt'},
+ chrome.test.callbackPass(function(entry) {
+ chrome.test.assertEq('new_file.txt', entry.name);
+ }),
+ );
+ },
+]);
Original Bug Report
Potential local file disclosure via pre-filled filename in chrome.fileSystem.chooseEntry
Flapjack, an experimental security project, has identified the following potential security issue.
Overview: The chrome.fileSystem.chooseEntry extension API lacks a user gesture requirement and allows a suggestedName to pre-fill the filename in “Open” dialogs. A malicious or compromised extension can exploit this via UI redress, tricking a user into inadvertently selecting and disclosing a sensitive local file.
Affected files:
extensions/browser/api/file_system/file_system_api.ccchrome/browser/extensions/api/file_system/file_entry_picker.cc
Estimated timestamp from git blame: 2023-03-01
Description
The chrome.fileSystem.chooseEntry extension API (implemented in FileSystemChooseEntryFunction in extensions/browser/api/file_system/file_system_api.cc) allows a caller to specify a suggestedName in the ChooseEntryOptions. This suggested name is processed and utilized even when the picker type is openFile or openWritableFile.
In extensions/browser/api/file_system/file_system_api.cc:
- The
Run()method processes the user-providedsuggested_nameviaBuildSuggestion. - It then calls
CalculateInitialPathAndShowPicker, which appends the suggested name to the initial directory path. - This combined path is passed to
ShowSelectFileDialogin theFileSystemDelegate.
In the Chrome-specific implementation (chrome/browser/extensions/api/file_system/file_entry_picker.cc), this path is passed as the default_path to the native SelectFileDialog::SelectFile. On most platforms, including Windows, this causes the native “Open” dialog to pre-fill the “File name” box with the provided filename, even though it is an “Open” dialog rather than a “Save” dialog.
Crucially, FileSystemChooseEntryFunction::Run() lacks a browser-side check for a user gesture (e.g., it is missing a user_gesture() or EXTENSION_FUNCTION_VALIDATE(has_user_gesture()) call). This allows a compromised extension renderer with the fileSystem permission (such as the PDF Viewer, or a malicious platform app) to trigger the file picker dialog unexpectedly at any time.
Potential Attack Scenario
(Note: These are potential steps based on static analysis, as our tooling does not currently run exploit code.)
- An attacker compromises an extension with the
fileSystempermission or convinces the user to install a malicious platform app. - The attacker predicts the filename of a sensitive file in a known default directory (e.g., a credential file).
- The attacker’s extension prompts the user to engage in an activity requiring rapid or sustained key presses (e.g., repeatedly pressing the “Enter” key in a game).
- While the user is pressing “Enter”, the attacker’s JavaScript programmatically calls
chrome.fileSystem.chooseEntry({type: 'openFile', suggestedName: 'credentials.json'}). - Because there is no user gesture requirement, the native “Open File” dialog appears instantly, stealing window focus.
- The “File name” input box is pre-filled with
credentials.json. - The user’s ongoing “Enter” keystroke is consumed by the dialog’s default “Open” button before they can react.
- The dialog closes, and the API returns a
FileEntryfor the sensitive file to the attacker’s JavaScript callback, granting unauthorized read access.
Suggested Fix
- Require a User Gesture: Add a check for user activation in
FileSystemChooseEntryFunction::Run(). For example:if (!user_gesture()) { return RespondNow(Error("User gesture is required")); } - Ignore
suggestedNamefor Open Dialogs: Modify the API implementation so that thesuggestedNameparameter is only used when the picker type iskSaveFile. ForkOpenFileandkOpenWritableFile, thesuggestedNameshould be ignored so the native dialog does not pre-fill a filename.
Evaluated with Chrome root at commit: 876d480da1f794d87813cfa2e6ff4fcf9771e939
Results 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.