fix: constrain AllowUniversalAccessFromFileURLs to file: origins in agent cluster key assignment (#50789)

* fix: constrain AllowUniversalAccessFromFileURLs to file: origins in agent cluster key assignment

Fixes #50242

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* sync patch with upstream CL

* add test

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Calvin
2026-04-30 06:36:50 -07:00
committed by GitHub
parent 4e56da6d30
commit 212b53c63e
3 changed files with 172 additions and 0 deletions

View File

@@ -152,3 +152,4 @@ chore_register_node_as_a_dynamic_trace_category_prefix.patch
fix_make_macos_text_replacement_work_on_contenteditable.patch
fix_allow_reentrancy_on_downloadmanagerimpl_observer_list.patch
build_gn_arg_to_support_linker_wrapper_script_on_windows.patch
fix_constrain_allowuniversalaccessfromfileurls_to_file_origins_in.patch

View File

@@ -0,0 +1,142 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Calvin Watford <cwatford@makenotion.com>
Date: Sun, 12 Apr 2026 21:24:57 -0700
Subject: fix: constrain AllowUniversalAccessFromFileURLs to file: origins in
agent assignment
HasPotentialUniversalAccessPrivilege() was treating the
AllowUniversalAccessFromFileURLs setting as unconditional, returning
true for all origins regardless of scheme. This caused non-file origins
(http://, https://, and custom schemes) to lose their browser-provided
AgentClusterKey when the setting was enabled, overriding it with a
universal file agent key and routing them to universal_access_agent_
which carries no key.
Electron enables AllowUniversalAccessFromFileURLs in all renderers via
the grant_file_protocol_extra_privileges fuse (on by default). After
https://crrev.com/c/7079680 moved cross-origin isolation status to the
renderer's per-context agent cluster key, this caused
self.crossOriginIsolated to return false even with COOP + COEP headers
correctly set on non-file origins.
The fix splits the single has_potential_universal_access_privilege
boolean in WindowAgentFactory::GetAgentForAgentClusterKey() into two
separate parameters:
- web_security_disabled: applies to all origins when web security is
off (--disable-web-security, --run-web-tests).
- allow_universal_access_from_file_urls: only takes effect for file:
scheme origins (those with IsUniversalFileAgent() agent cluster
keys), preserving their existing routing to universal_access_agent_.
This aligns the agent assignment logic with the origin privilege
granting code in the same file (DocumentLoader::CalculateOrigin), which
already correctly gates AllowUniversalAccessFromFileURLs behind
origin->IsLocal().
This patch should be upstreamed, with updates to surrounding doc
comments + tests.
Upstream CL: https://chromium-review.googlesource.com/c/chromium/src/+/7795303
Chromium bug: https://crbug.com/505299810
diff --git a/third_party/blink/renderer/core/execution_context/window_agent_factory.cc b/third_party/blink/renderer/core/execution_context/window_agent_factory.cc
index 86ce2d493dc833dd1601787fc042b8a4a612fc2e..b9bd1f459f240401d7a6b0bce03cf82b6b54e9fc 100644
--- a/third_party/blink/renderer/core/execution_context/window_agent_factory.cc
+++ b/third_party/blink/renderer/core/execution_context/window_agent_factory.cc
@@ -20,19 +20,27 @@ WindowAgentFactory::WindowAgentFactory(
AgentGroupScheduler& agent_group_scheduler)
: agent_group_scheduler_(agent_group_scheduler) {}
+WindowAgent* WindowAgentFactory::GetOrCreateUniversalAccessAgent() {
+ if (!universal_access_agent_) {
+ universal_access_agent_ =
+ MakeGarbageCollected<WindowAgent>(*agent_group_scheduler_);
+ }
+ return universal_access_agent_.Get();
+}
+
WindowAgent* WindowAgentFactory::GetAgentForAgentClusterKey(
- bool has_potential_universal_access_privilege,
+ bool is_web_security_disabled,
+ bool allow_universal_access_from_file_urls,
const AgentClusterKey& agent_cluster_key) {
- if (has_potential_universal_access_privilege) {
- if (!universal_access_agent_) {
- universal_access_agent_ =
- MakeGarbageCollected<WindowAgent>(*agent_group_scheduler_);
- }
- return universal_access_agent_.Get();
+ if (is_web_security_disabled) {
+ return GetOrCreateUniversalAccessAgent();
}
// For `file:` scheme origins.
if (agent_cluster_key.IsUniversalFileAgent()) {
+ if (allow_universal_access_from_file_urls) {
+ return GetOrCreateUniversalAccessAgent();
+ }
if (!file_url_agent_) {
file_url_agent_ = MakeGarbageCollected<WindowAgent>(
*agent_group_scheduler_, agent_cluster_key);
diff --git a/third_party/blink/renderer/core/execution_context/window_agent_factory.h b/third_party/blink/renderer/core/execution_context/window_agent_factory.h
index b5464975a3c365a29e342b0dd57a8a73a311924a..72ebd275330e61a8abebfe6d2c06f42544a83bec 100644
--- a/third_party/blink/renderer/core/execution_context/window_agent_factory.h
+++ b/third_party/blink/renderer/core/execution_context/window_agent_factory.h
@@ -40,12 +40,15 @@ class WindowAgentFactory final : public GarbageCollected<WindowAgentFactory> {
// * --run-web-tests is set,
// * or, the Blink instance is running for Android WebView.
WindowAgent* GetAgentForAgentClusterKey(
- bool has_potential_universal_access_privilege,
+ bool is_web_security_disabled,
+ bool allow_universal_access_from_file_urls,
const AgentClusterKey& agent_cluster_key);
void Trace(Visitor*) const;
private:
+ WindowAgent* GetOrCreateUniversalAccessAgent();
+
// Use a shared instance of Agent for all frames if a frame may have the
// universal access privilege.
WeakMember<WindowAgent> universal_access_agent_;
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 2344e1d62f4a2d7fdf139b64b6c71ede1e3094ce..f79f870408a71e107933219f30ea6dc3afaa8716 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -2600,22 +2600,16 @@ bool ShouldReuseDOMWindow(LocalDOMWindow* window,
return *window_coi_key == *navigation_coi_key;
}
-namespace {
-
-bool HasPotentialUniversalAccessPrivilege(LocalFrame* frame) {
- return !frame->GetSettings()->GetWebSecurityEnabled() ||
- frame->GetSettings()->GetAllowUniversalAccessFromFileURLs();
-}
-
-} // namespace
-
WindowAgent* GetWindowAgentForAgentClusterKey(
LocalFrame* frame,
const AgentClusterKey& agent_cluster_key) {
// TODO(keishi): Also check if AllowUniversalAccessFromFileURLs might
// dynamically change.
+ Settings* settings = frame->GetSettings();
return frame->window_agent_factory().GetAgentForAgentClusterKey(
- HasPotentialUniversalAccessPrivilege(frame), agent_cluster_key);
+ /*is_web_security_disabled=*/!settings->GetWebSecurityEnabled(),
+ /*allow_universal_access_from_file_urls=*/
+ settings->GetAllowUniversalAccessFromFileURLs(), agent_cluster_key);
}
// Inheriting cases use their agent's AgentClusterKey value, which is set
@@ -2716,7 +2710,7 @@ void DocumentLoader::InitializeWindow(Document* owner_document) {
// WindowAgentFactory::GetAgentForOrigin(), as the two conditions below hand
// out universal WindowAgent objects, and thus override the AgentClusterKey
// provided by the browser process.
- } else if (HasPotentialUniversalAccessPrivilege(frame_.Get()) ||
+ } else if (!frame_->GetSettings()->GetWebSecurityEnabled() ||
security_origin->IsLocal()) {
// In this case we either have AllowUniversalAccessFromFileURLs enabled, or
// WebSecurity is disabled, or it's a local scheme such as file://; any of

View File

@@ -263,6 +263,35 @@ describe('focus handling', () => {
});
});
describe('cross origin isolation', () => {
afterEach(closeAllWindows);
let server: http.Server;
let serverUrl: string;
before(async () => {
server = http.createServer((_req, res) => {
res.setHeader('Content-Type', 'text/html');
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
res.end('<!doctype html>');
});
serverUrl = (await listen(server)).url;
});
after(() => {
server.close();
});
it('is enabled by COOP and COEP headers', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL(serverUrl);
const crossOriginIsolated = await w.webContents.executeJavaScript('globalThis.crossOriginIsolated');
expect(crossOriginIsolated).to.equal(true);
});
});
describe('web security', () => {
afterEach(closeAllWindows);
let server: http.Server;