Medium watch CVSS 6.5 chrome Spoofing 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in Extensions
DescriptionUI misrepresentation in Extensions
ComponentExtensions
Sub-componentFileSystem
Bug ClassSpoofing
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.
Tracker497538341
Fix commit8fdebd5b1fb9 (chromium/src) +75/-2
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-03-29 (per vendor advisory)
Fix landed2026-07-01 (public git)
Disclosed2026-08-25
Patch gap55 days — fix public in source before the advisory

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::SelectFileDialog picker kind; SELECT_SAVEAS_FILE is the “save as” dialog, distinct from open-file and open-directory pickers.
`file_type_info` / accept filter
The accept-type filter built from suggested_extension and options.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.

Key insight
The single mistake was applying the caller’s 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

  1. Malicious/abusive app calls chooseEntry A platform app invokes chrome.fileSystem.chooseEntry with an openFile/openDirectory/openWritableFile type and a crafted suggestedName.
  2. Picker opens pre-filled Because pre-patch code appended suggestedName regardless of picker_type, the native open/directory dialog opens with the filename input already populated with the attacker-chosen name.
  3. 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.
  4. 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 openDirectory picker previously derived the entry from suggestedName, which the fix stops.

Impact Assessment

An attacker (a malicious or misconfigured platform app) gains UI misrepresentation: the file-open/directory picker is pre-populated with an app-chosen filename, potentially misleading the user about what entry they are selecting. This occurs in the browser’s file-dialog UI and requires the user to interact with the picker; the advisory classifies it as UI misrepresentation/spoofing in Extensions with no exploited-in-the-wild status stated. Any resulting unauthorized file access beyond the misleading default is (inferred).

Changed Functions

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

Files Changed

  • chrome/browser/extensions/api/file_system/file_system_apitest.cc
  • extensions/browser/api/file_system/file_system_api.cc
  • extensions/common/api/file_system.idl
  • chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js
  • 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/test.html
  • chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json

Audit Directions

  • Caller-supplied defaults in native dialogs
    Review 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 conditioning
    Search for file/dialog options applied without branching on SELECT_SAVEAS_FILE vs. open/directory modes, where a save-only affordance may leak into open flows.
  • Spoofable default selection
    Audit UI flows where an app-provided string pre-selects or pre-fills a user choice, enabling misrepresentation of the user’s intended action.
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);
+        }),
+    );
+  },
+]);
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