Chrome · WebMCP
CVE-2026-87521
Logic Error in WebMCP
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
CorsExploitBrowserTestcontent/browser/security_exploit_browsertest.cc |
modified |
Files Changed
content/browser/renderer_host/model_context_user_data.cccontent/browser/security_exploit_browsertest.cc
Patch
From b4983713ccc6cc723c7bf3d5c5228dd52f7ce9b1 Mon Sep 17 00:00:00 2001 From: François Beaufort <[email protected]> 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 <[email protected]> Commit-Queue: Fr <[email protected]> Reviewed-by: Alex Moshchuk <[email protected]> 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.
References
On This Page