Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in WebMCP
DescriptionInformation leak in WebMCP
ComponentWebMCP
Bug ClassLogic Error
Tracker512986143
Fix commitb4983713ccc6 (chromium/src) +51/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
CorsExploitBrowserTest
content/browser/security_exploit_browsertest.cc
modified

Files Changed

  • content/browser/renderer_host/model_context_user_data.cc
  • content/browser/security_exploit_browsertest.cc
From b4983713ccc6cc723c7bf3d5c5228dd52f7ce9b1 Mon Sep 17 00:00:00 2001
From: François Beaufort <beaufort.francois@gmail.com>
Date: Thu, 30 Jul 2026 23:51:18 -0700
Subject: [PATCH] [WebMCP] clear exposed_origins to prevent leakage to renderers

Bug: 512986143, 512533939
Change-Id: I67bc4e762503bd73fde091f71c418455a0291f00
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8137431
Reviewed-by: Dominic Farolino <dom@chromium.org>
Commit-Queue: Fr <beaufort.francois@gmail.com>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1671656}
---

diff --git a/content/browser/renderer_host/model_context_user_data.cc b/content/browser/renderer_host/model_context_user_data.cc
index dcbce4ed..965a68f 100644
--- a/content/browser/renderer_host/model_context_user_data.cc
+++ b/content/browser/renderer_host/model_context_user_data.cc
@@ -240,6 +240,11 @@
                ->GetFrameTokenForSiteInstanceGroup(site_instance_group);
 
       cloned_tool->tool_owner_frame_token = token;
+
+      // Clear `exposed_origins` to prevent leaking the full list of
+      // authorized origins to the calling renderer process.
+      cloned_tool->exposed_origins.clear();
+
       all_tools.push_back(std::move(cloned_tool));
     }
     return RenderFrameHost::FrameIterationAction::kContinue;
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index c1d86a44..9909662 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -906,6 +906,52 @@
   EXPECT_EQ(bad_message::RFHI_WEBMCP_OPAQUE_TARGET_ORIGIN, kill_waiter.Wait());
 }
 
+// Test that `GetScriptTools()` clears `exposed_origins` so that the full ACL
+// list is not leaked to caller renderers.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWebMCPEnabled,
+                       GetScriptToolsExposedOriginsRedacted) {
+  GURL start_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), start_url));
+
+  RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+
+  EXPECT_TRUE(
+      rfh->IsFeatureEnabled(network::mojom::PermissionsPolicyFeature::kTools));
+
+  auto tool = blink::mojom::ScriptTool::New();
+  tool->name = "test-tool";
+  tool->description = "test description";
+  tool->tool_owner_frame_token = rfh->GetFrameToken();
+  tool->origin = rfh->GetLastCommittedOrigin();
+  tool->exposed_origins.push_back(
+      url::Origin::Create(GURL("https://friend.example")));
+  tool->exposed_origins.push_back(
+      url::Origin::Create(GURL("https://attacker.example")));
+
+  mojo::Remote<blink::mojom::ModelContextHost> script_tool_host;
+  rfh->BindModelContextHost(script_tool_host.BindNewPipeAndPassReceiver());
+
+  base::RunLoop register_loop;
+  script_tool_host->RegisterScriptTool(std::move(tool),
+                                       register_loop.QuitClosure());
+  register_loop.Run();
+
+  base::RunLoop get_tools_loop;
+  std::vector<blink::mojom::ScriptToolPtr> result_tools;
+  script_tool_host->GetScriptTools(
+      {}, base::BindLambdaForTesting(
+              [&](std::vector<blink::mojom::ScriptToolPtr> tools) {
+                result_tools = std::move(tools);
+                get_tools_loop.Quit();
+              }));
+  get_tools_loop.Run();
+
+  ASSERT_EQ(1u, result_tools.size());
+  EXPECT_EQ("test-tool", result_tools[0]->name);
+  EXPECT_TRUE(result_tools[0]->exposed_origins.empty());
+}
+
 class CorsExploitBrowserTest : public ContentBrowserTest {
  public:
   CorsExploitBrowserTest() = default;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index c1d86a44..9909662 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -906,6 +906,52 @@
   EXPECT_EQ(bad_message::RFHI_WEBMCP_OPAQUE_TARGET_ORIGIN, kill_waiter.Wait());
 }
 
+// Test that `GetScriptTools()` clears `exposed_origins` so that the full ACL
+// list is not leaked to caller renderers.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWebMCPEnabled,
+                       GetScriptToolsExposedOriginsRedacted) {
+  GURL start_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), start_url));
+
+  RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+
+  EXPECT_TRUE(
+      rfh->IsFeatureEnabled(network::mojom::PermissionsPolicyFeature::kTools));
+
+  auto tool = blink::mojom::ScriptTool::New();
+  tool->name = "test-tool";
+  tool->description = "test description";
+  tool->tool_owner_frame_token = rfh->GetFrameToken();
+  tool->origin = rfh->GetLastCommittedOrigin();
+  tool->exposed_origins.push_back(
+      url::Origin::Create(GURL("https://friend.example")));
+  tool->exposed_origins.push_back(
+      url::Origin::Create(GURL("https://attacker.example")));
+
+  mojo::Remote<blink::mojom::ModelContextHost> script_tool_host;
+  rfh->BindModelContextHost(script_tool_host.BindNewPipeAndPassReceiver());
+
+  base::RunLoop register_loop;
+  script_tool_host->RegisterScriptTool(std::move(tool),
+                                       register_loop.QuitClosure());
+  register_loop.Run();
+
+  base::RunLoop get_tools_loop;
+  std::vector<blink::mojom::ScriptToolPtr> result_tools;
+  script_tool_host->GetScriptTools(
+      {}, base::BindLambdaForTesting(
+              [&](std::vector<blink::mojom::ScriptToolPtr> tools) {
+                result_tools = std::move(tools);
+                get_tools_loop.Quit();
+              }));
+  get_tools_loop.Run();
+
+  ASSERT_EQ(1u, result_tools.size());
+  EXPECT_EQ("test-tool", result_tools[0]->name);
+  EXPECT_TRUE(result_tools[0]->exposed_origins.empty());
+}
+
 class CorsExploitBrowserTest : public ContentBrowserTest {
  public:
   CorsExploitBrowserTest() = default;
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.