Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in WebShare
DescriptionInsufficient validation of untrusted input in WebShare
ComponentWebShare
Bug ClassLogic Error
Tracker498977444
Fix commit3f267f3cd2d3 (chromium/src) +31/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
chrome/browser/webshare/share_service_impl.cc
modified
TEST_F
chrome/browser/webshare/share_service_unittest.cc
modified
BindLambdaForTesting
chrome/browser/webshare/share_service_unittest.cc
modified

Files Changed

  • chrome/browser/webshare/share_service_impl.cc
  • chrome/browser/webshare/share_service_unittest.cc
From 3f267f3cd2d398153685b93e619d360bba561d5f Mon Sep 17 00:00:00 2001
From: Dan Murphy <dmurph@chromium.org>
Date: Mon, 04 May 2026 17:04:04 -0700
Subject: [PATCH] [WebShare] Add URL scheme validation in browser process

A compromised renderer could bypass Web Share API validations by
directly sending Mojo messages to the browser process, supplying a
`file://` URL pointing to a sensitive local file.

This CL adds independent validation in `ShareServiceImpl::Share` to
ensure the `share_url` uses a permitted scheme (`http` or `https`) and
explicitly rejects others like `file://` by reporting a bad message and
terminating the renderer.

Bug: 498977444
Change-Id: Ibc957bd0b41e33a9033a1b4e84f78907f7ecf613
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7813645
Auto-Submit: Daniel Murphy <dmurph@chromium.org>
Commit-Queue: Dibyajyoti Pal <dibyapal@chromium.org>
Commit-Queue: Daniel Murphy <dmurph@chromium.org>
Reviewed-by: Dibyajyoti Pal <dibyapal@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1625029}
---

diff --git a/chrome/browser/webshare/share_service_impl.cc b/chrome/browser/webshare/share_service_impl.cc
index 805e2ba..158d69f6 100644
--- a/chrome/browser/webshare/share_service_impl.cc
+++ b/chrome/browser/webshare/share_service_impl.cc
@@ -184,6 +184,13 @@
     return;
   }
 
+  if (!share_url.is_empty() && !share_url.SchemeIsHTTPOrHTTPS()) {
+    std::move(callback).Run(blink::mojom::ShareError::PERMISSION_DENIED);
+    ReportBadMessageAndDeleteThis(
+        "Web Share URL scheme must be http or https.");
+    return;
+  }
+
   content::WebContents* const web_contents =
       content::WebContents::FromRenderFrameHost(&render_frame_host());
   if (!web_contents) {
diff --git a/chrome/browser/webshare/share_service_unittest.cc b/chrome/browser/webshare/share_service_unittest.cc
index fa6b254..338ef6b7 100644
--- a/chrome/browser/webshare/share_service_unittest.cc
+++ b/chrome/browser/webshare/share_service_unittest.cc
@@ -20,6 +20,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_renderer_host.h"
 #include "storage/browser/blob/blob_data_builder.h"
 #include "storage/browser/blob/blob_impl.h"
 #include "storage/browser/blob/blob_storage_context.h"
@@ -163,6 +164,7 @@
 #if BUILDFLAG(IS_WIN)
   webshare::ScopedShareOperationFakeComponents scoped_fake_components_;
 #endif
+ protected:
   mojo::Remote<blink::mojom::ShareService> share_service_remote_;
 };
 
@@ -214,6 +216,28 @@
   EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".webm", "video/webm"));
 }
 
+TEST_F(ShareServiceUnitTest, ShareInvalidURLScheme) {
+  const std::string kTitle = "Title";
+  const std::string kText = "Text";
+  const GURL kUrl = GURL("file:///etc/passwd");
+  std::vector<blink::mojom::SharedFilePtr> files;
+
+  base::RunLoop run_loop;
+  share_service_remote_.set_disconnect_handler(run_loop.QuitClosure());
+
+  bool callback_called = false;
+  share_service_remote_->Share(
+      kTitle, kText, kUrl, std::move(files),
+      base::BindLambdaForTesting([&callback_called](ShareError error) {
+        callback_called = true;
+        EXPECT_EQ(error, ShareError::PERMISSION_DENIED);
+      }));
+
+  run_loop.Run();
+  EXPECT_TRUE(callback_called);
+  EXPECT_FALSE(share_service_remote_.is_connected());
+}
+
 TEST_F(ShareServiceUnitTest, PortableDocumentFormat) {
   EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".pdf", "application/pdf"));
 }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/webshare/share_service_unittest.cc b/chrome/browser/webshare/share_service_unittest.cc
index fa6b254..338ef6b7 100644
--- a/chrome/browser/webshare/share_service_unittest.cc
+++ b/chrome/browser/webshare/share_service_unittest.cc
@@ -20,6 +20,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_renderer_host.h"
 #include "storage/browser/blob/blob_data_builder.h"
 #include "storage/browser/blob/blob_impl.h"
 #include "storage/browser/blob/blob_storage_context.h"
@@ -163,6 +164,7 @@
 #if BUILDFLAG(IS_WIN)
   webshare::ScopedShareOperationFakeComponents scoped_fake_components_;
 #endif
+ protected:
   mojo::Remote<blink::mojom::ShareService> share_service_remote_;
 };
 
@@ -214,6 +216,28 @@
   EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".webm", "video/webm"));
 }
 
+TEST_F(ShareServiceUnitTest, ShareInvalidURLScheme) {
+  const std::string kTitle = "Title";
+  const std::string kText = "Text";
+  const GURL kUrl = GURL("file:///etc/passwd");
+  std::vector<blink::mojom::SharedFilePtr> files;
+
+  base::RunLoop run_loop;
+  share_service_remote_.set_disconnect_handler(run_loop.QuitClosure());
+
+  bool callback_called = false;
+  share_service_remote_->Share(
+      kTitle, kText, kUrl, std::move(files),
+      base::BindLambdaForTesting([&callback_called](ShareError error) {
+        callback_called = true;
+        EXPECT_EQ(error, ShareError::PERMISSION_DENIED);
+      }));
+
+  run_loop.Run();
+  EXPECT_TRUE(callback_called);
+  EXPECT_FALSE(share_service_remote_.is_connected());
+}
+
 TEST_F(ShareServiceUnitTest, PortableDocumentFormat) {
   EXPECT_EQ(ShareError::OK, ShareGeneratedFileData(".pdf", "application/pdf"));
 }
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Arbitrary Local File Exfiltration via Web Share API on macOS

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 security team.

Overview: A compromised renderer process can bypass Web Share API validations by directly sending Mojo messages to the browser process. Because the browser process lacks its own URL scheme validation, an attacker can supply a file:// URL pointing to a sensitive local file. On macOS, this file is passed to the system share sheet with a spoofed title, potentially tricking the user into exfiltrating the file’s contents.

Affected files:

  • chrome/browser/webshare/share_service_impl.cc
  • content/app_shim_remote_cocoa/render_widget_host_ns_view_bridge.mm
  • chrome/browser/webshare/mac/sharing_service_operation.mm

Estimated timestamp from git blame: 2025-08-04

Summary

A vulnerability in the Web Share API implementation allows a compromised renderer process to bypass security checks and initiate the sharing of arbitrary local files. By directly invoking the blink::mojom::ShareService::Share Mojo interface, an attacker can avoid renderer-side URL scheme and user activation checks. The browser process (ShareServiceImpl::Share) fails to independently validate the URL scheme, allowing a file:// URL to be passed to the platform’s native sharing UI. On macOS 13.0+, an attacker can spoof the title in the share sheet, tricking the user into sending the local file to a target application like Mail or AirDrop.

Potential Attack Steps

Note: These are potential steps based on code analysis; a working proof-of-concept has not been executed.

  1. Renderer Compromise: An attacker gains arbitrary code execution within the sandboxed renderer process.
  2. Direct Mojo Invocation: The attacker binds directly to the blink::mojom::ShareService and sends a Share message. This bypasses the renderer-side checks in NavigatorShare::share and NavigatorShare::CanShareInternal, which would normally require transient user activation and restrict URLs to HTTP/HTTPS.
  3. Payload Construction: The attacker provides a deceptive title (e.g., “Cute Kitten Image”), an empty files array, and sets share_url to a sensitive local file path using the file:// scheme (e.g., file:///Users/victim/.ssh/id_rsa).
  4. Browser Process Bypass: The browser process receives the message in ShareServiceImpl::Share (chrome/browser/webshare/share_service_impl.cc). Because the files array is empty, it skips Safe Browsing and MIME type checks. Crucially, the browser process does not validate the share_url scheme or check for transient user activation, trusting the renderer implicitly.
  5. macOS UI Spoofing: The request is routed to macOS UI components. In RenderWidgetHostNSViewBridge::ShowSharingServicePicker (content/app_shim_remote_cocoa/render_widget_host_ns_view_bridge.mm), the file:// GURL is converted to a native NSURL. It is then wrapped in an NSPreviewRepresentingActivityItem alongside the attacker’s deceptive title.
  6. Exfiltration: The macOS system share sheet appears without warning, displaying the spoofed title. If the user selects a sharing target (e.g., Mail), the macOS sharing system reads the local file specified by the NSURL and attaches its contents, achieving local file exfiltration and a sandbox escape.

Suggested Fix

The browser process must not rely solely on the renderer for security validation.

  1. URL Scheme Validation: Add validation in ShareServiceImpl::Share (chrome/browser/webshare/share_service_impl.cc) to ensure share_url uses a permitted scheme (e.g., http: or https:) and explicitly reject file://, chrome://, etc.
  2. User Activation Check: Enforce that a transient user activation is present when handling the Share Mojo request in the browser process.

Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33


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