Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in Extensions
DescriptionUI misrepresentation in Extensions
ComponentExtensions
Bug ClassLogic Error
Tracker497538341
Fix commit8fdebd5b1fb9 (chromium/src) +75/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
IN_PROC_BROWSER_TEST_F
chrome/browser/extensions/api/file_system/file_system_apitest.cc
modified
if
extensions/browser/api/file_system/file_system_api.cc
modified

Files Changed

  • chrome/browser/extensions/api/file_system/file_system_apitest.cc
  • chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
  • chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js
  • chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json
  • chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html
  • chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js
  • extensions/browser/api/file_system/file_system_api.cc
  • extensions/common/api/file_system.idl
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/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);
+        }),
+    );
+  },
+]);
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
Loading diff…

Regression Test / PoC

shipped with the fix
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);
+        }),
+    );
+  },
+]);
Loading diff…

Original Bug Report

reported by rj...@google.com

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.cc
  • chrome/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-provided suggested_name via BuildSuggestion.
  • It then calls CalculateInitialPathAndShowPicker, which appends the suggested name to the initial directory path.
  • This combined path is passed to ShowSelectFileDialog in the FileSystemDelegate.

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.)

  1. An attacker compromises an extension with the fileSystem permission or convinces the user to install a malicious platform app.
  2. The attacker predicts the filename of a sensitive file in a known default directory (e.g., a credential file).
  3. 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).
  4. While the user is pressing “Enter”, the attacker’s JavaScript programmatically calls chrome.fileSystem.chooseEntry({type: 'openFile', suggestedName: 'credentials.json'}).
  5. Because there is no user gesture requirement, the native “Open File” dialog appears instantly, stealing window focus.
  6. The “File name” input box is pre-filled with credentials.json.
  7. The user’s ongoing “Enter” keystroke is consumed by the dialog’s default “Open” button before they can react.
  8. The dialog closes, and the API returns a FileEntry for the sensitive file to the attacker’s JavaScript callback, granting unauthorized read access.

Suggested Fix

  1. 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"));
    }
    
  2. Ignore suggestedName for Open Dialogs: Modify the API implementation so that the suggestedName parameter is only used when the picker type is kSaveFile. For kOpenFile and kOpenWritableFile, the suggestedName should 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.

View on issue tracker