CVE-2026-11283
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forchrome/browser/app_controller_mac.mm |
modified | |
ifchrome/browser/app_controller_mac.mm |
modified |
Files Changed
chrome/browser/app_controller_mac.mmchrome/browser/app_controller_mac_browsertest.mm
Patch
From ae41c6e4f6a8a98c536cb2e70b4b71c4e560c2c6 Mon Sep 17 00:00:00 2001
From: Dibyajyoti Pal <dibyapal@google.com>
Date: Tue, 14 Apr 2026 15:19:26 -0700
Subject: [PATCH] [PWA/Shortcuts] Disallow shortcut creation for sensitive chrome urls
The "Create Shortcut" flow would previously only work for sites that had
http/https in them, and wouldn't allow chrome:// sites. This CL
streamlines that behavior by using startup::ValidateUrl, which
allowlists urls that are non-sensitive and can be launched by Chrome.
For shortcuts created on Mac via .crwebloc files, there was nothing
stopping Chrome from opening Chrome sensitive urls if shortcuts were
created on them, so this CL fixes that, by ensuring that .crwebloc files
do not open these urls on Mac at all.
Verified with an automated test, and manually testing that this behavior
is not possible on Linux and Windows, and hence doesn't need fixing
there. On CrOS, the concept of desktop shortcuts don't exist.
Fixed: 502069297
Include-Ci-Only-Tests: chromium.mac:mac15-x64-rel-tests|browser_tests
Change-Id: I7b48645dea7f7d66acd20389d9cf0ad20517f413
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7757382
Reviewed-by: Avi Drissman <avi@chromium.org>
Commit-Queue: Dibyajyoti Pal <dibyapal@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1614742}
---
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 77e2f77..1bb7d48 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -127,6 +127,7 @@
#include "content/public/browser/download_manager.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
+#include "extensions/buildflags/buildflags.h"
#include "net/base/apple/url_conversions.h"
#include "net/base/filename_util.h"
#import "ui/base/cocoa/nsmenu_additions.h"
@@ -434,8 +435,22 @@
base::flat_map<base::FilePath, std::vector<GURL>> profile_url_map;
for (const auto& path : shortcuts) {
auto shortcut = shortcuts::ChromeWeblocFile::LoadFromFile(path);
+ // TODO: Consider opening the original file URL?
if (!shortcut.has_value()) {
- // TODO: Consider opening the original file URL?
+ continue;
+ }
+ bool is_shortcut_url_valid =
+ startup::ValidateUrl(shortcut->target_url());
+ // Do not allow chrome sensitive urls to be launched from a .crwebloc
+ // file.
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ is_shortcut_url_valid =
+ is_shortcut_url_valid || shortcut->target_url().SchemeIs(
+ extensions::kExtensionScheme);
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+ if (!is_shortcut_url_valid) {
+ LOG(ERROR) << "Not allowed to open target url: "
+ << shortcut->target_url();
continue;
}
profile_url_map[shortcut->profile_path_name().path()].push_back(
diff --git a/chrome/browser/app_controller_mac_browsertest.mm b/chrome/browser/app_controller_mac_browsertest.mm
index 919af1f..8b3119f 100644
--- a/chrome/browser/app_controller_mac_browsertest.mm
+++ b/chrome/browser/app_controller_mac_browsertest.mm
@@ -25,6 +25,7 @@
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
+#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
@@ -964,6 +965,45 @@
}
IN_PROC_BROWSER_TEST_F(AppControllerShortcutsNotAppsBrowserTest,
+ DisallowChromeUrlWeblocFile) {
+ // Ensure the AppController is the NSApp delegate.
+ std::ignore = AppController.sharedController;
+
+ // Create and open a .crwebloc file with a chrome:// URL.
+ GURL chrome_url("chrome://settings/");
+ base::ScopedTempDir temp_dir;
+ base::FilePath crwebloc_file;
+ {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ crwebloc_file = temp_dir.GetPath().AppendASCII("test_shortcut.crwebloc");
+ ASSERT_TRUE(shortcuts::ChromeWeblocFile(
+ chrome_url, *base::SafeBaseName::Create(
+ browser()->profile()->GetPath()))
+ .SaveToFile(crwebloc_file));
+ }
+
+ int initial_tab_count = browser()->tab_strip_model()->count();
+ content::WebContents* current_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ SendOpenUrlToAppController(net::FilePathToFileURL(crwebloc_file));
+ // Wait for any background tasks to complete and send its replies to the UI
+ // thread. This helps ensure that SendOpenUrlToAppController() completes.
+ base::ThreadPoolInstance::Get()->FlushForTesting();
+ base::RunLoop().RunUntilIdle();
+
+ // It should not be opened in the browser.
+ EXPECT_EQ(initial_tab_count, browser()->tab_strip_model()->count());
+ EXPECT_NE(chrome_url, current_contents->GetLastCommittedURL());
+
+ {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ EXPECT_TRUE(temp_dir.Delete());
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(AppControllerShortcutsNotAppsBrowserTest,
OpenChromeWeblocFileInSecondProfile) {
ASSERT_TRUE(embedded_test_server()->Start());
// Ensure the AppController is the NSApp delegate.
Regression Test / PoC
diff --git a/chrome/browser/app_controller_mac_browsertest.mm b/chrome/browser/app_controller_mac_browsertest.mm
index 919af1f..8b3119f 100644
--- a/chrome/browser/app_controller_mac_browsertest.mm
+++ b/chrome/browser/app_controller_mac_browsertest.mm
@@ -25,6 +25,7 @@
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
+#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
@@ -964,6 +965,45 @@
}
IN_PROC_BROWSER_TEST_F(AppControllerShortcutsNotAppsBrowserTest,
+ DisallowChromeUrlWeblocFile) {
+ // Ensure the AppController is the NSApp delegate.
+ std::ignore = AppController.sharedController;
+
+ // Create and open a .crwebloc file with a chrome:// URL.
+ GURL chrome_url("chrome://settings/");
+ base::ScopedTempDir temp_dir;
+ base::FilePath crwebloc_file;
+ {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ crwebloc_file = temp_dir.GetPath().AppendASCII("test_shortcut.crwebloc");
+ ASSERT_TRUE(shortcuts::ChromeWeblocFile(
+ chrome_url, *base::SafeBaseName::Create(
+ browser()->profile()->GetPath()))
+ .SaveToFile(crwebloc_file));
+ }
+
+ int initial_tab_count = browser()->tab_strip_model()->count();
+ content::WebContents* current_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ SendOpenUrlToAppController(net::FilePathToFileURL(crwebloc_file));
+ // Wait for any background tasks to complete and send its replies to the UI
+ // thread. This helps ensure that SendOpenUrlToAppController() completes.
+ base::ThreadPoolInstance::Get()->FlushForTesting();
+ base::RunLoop().RunUntilIdle();
+
+ // It should not be opened in the browser.
+ EXPECT_EQ(initial_tab_count, browser()->tab_strip_model()->count());
+ EXPECT_NE(chrome_url, current_contents->GetLastCommittedURL());
+
+ {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ EXPECT_TRUE(temp_dir.Delete());
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(AppControllerShortcutsNotAppsBrowserTest,
OpenChromeWeblocFileInSecondProfile) {
ASSERT_TRUE(embedded_test_server()->Start());
// Ensure the AppController is the NSApp delegate.
Original Bug Report
macOS .crwebloc file handler bypasses startup URL scheme allowlist
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team.
Overview: The macOS-specific .crwebloc file handler in Chrome fails to validate extracted URLs against the startup scheme allowlist. This allows a malicious .crwebloc file to force Chrome to navigate to privileged internal URLs such as chrome:// and devtools://, bypassing restrictions designed to prevent untrusted external applications from accessing sensitive internal browser pages.
Affected files:
chrome/browser/shortcuts/chrome_webloc_file.mmchrome/browser/app_controller_mac.mm
Estimated timestamp from git blame: 2024-11-21
Description
A logic flaw in Chrome for macOS allows a crafted .crwebloc file to bypass the startup::ValidateUrl() scheme allowlist. This vulnerability enables external triggers (such as a user double-clicking a file in Finder) to force Chrome to navigate to privileged URLs, including chrome://, devtools://, and chrome-extension:// pages.
Chrome registers as the default macOS handler for .crwebloc files, which are property lists containing a URL and a profile name. When a .crwebloc file is opened via macOS LaunchServices, the browser receives a file:// URL pointing to the shortcut file.
While Chrome validates URLs using google-chrome:// and chromium:// schemes against startup::ValidateUrl(), it bypasses this validation for file:// URLs. The URL is then parsed from the .crwebloc file using shortcuts::ChromeWeblocFile::LoadFromFile(), which only checks if the extracted URL is syntactically valid (GURL::is_valid()).
The extracted URL is passed directly to the browser-initiated navigation flow in StartupBrowserCreatorImpl::OpenTabsInBrowser. Because the navigation is browser-initiated and the chrome:// scheme is natively handled by ProfileIOData::IsHandledURL, the navigation succeeds, bypassing security policies intended to prevent untrusted external applications from opening dangerous schemes.
Impact
This vulnerability allows a remote attacker to deliver a .crwebloc file (via AirDrop, USB, email, or a web download) that forces Chrome to navigate to a privileged URL in the user’s profile. This provides a reliable primitive for bypassing origin-based restrictions and serves as a precondition for exploiting WebUI XSS, DevTools vulnerabilities, or extension-page logic that assumes internal pages are unreachable from external sources.
Reproduction Steps (Potential)
- Create a file named
Shared_Document.crweblocwith the following content:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"><dict> <key>URL</key><string>chrome://settings/passwords</string> <key>CrProfile</key><string>Default</string> </dict></plist> - On a macOS system where Chrome is the default handler for
.crwebloc(which is the default behavior), double-click the file in Finder. - Observe that Chrome opens and navigates directly to
chrome://settings/passwords, demonstrating the bypass of the startup scheme allowlist.
Suggested Fix
Ensure that URLs extracted from .crwebloc files are validated against the standard startup scheme allowlist.
In chrome/browser/app_controller_mac.mm, update the OpenUrlsInBrowser function to apply startup::ValidateUrl() to the URLs loaded from the .crwebloc files before they are appended to the profile_url_map:
#include "chrome/browser/ui/startup/google_chrome_scheme_util.h"
// ... inside OpenUrlsInBrowser ...
for (const auto& path : shortcuts) {
auto shortcut = shortcuts::ChromeWeblocFile::LoadFromFile(path);
if (!shortcut.has_value()) {
// TODO: Consider opening the original file URL?
continue;
}
// ADD VALIDATION CHECK HERE:
if (!startup::ValidateUrl(shortcut->target_url())) {
continue;
}
profile_url_map[shortcut->profile_path_name().path()].push_back(
shortcut->target_url());
}
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
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.