From 3df3a6a736b93e0d69fa3b0c403b33f201287780 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 19 Dec 2025 23:08:40 +1300 Subject: [PATCH] fix: `webRequest.onBeforeSendHeaders` not being able to modify reserved headers (#49226) * fix: `webRequest.onBeforeSendHeaders` not being able to modify reserved headers * chore: add unit test for reserved header --- .../net/proxying_url_loader_factory.cc | 6 ++-- shell/common/api/electron_api_url_loader.cc | 8 +++++ spec/api-web-request-spec.ts | 31 ++++++++++++++++++- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/shell/browser/net/proxying_url_loader_factory.cc b/shell/browser/net/proxying_url_loader_factory.cc index 255687b869..e71de44a2c 100644 --- a/shell/browser/net/proxying_url_loader_factory.cc +++ b/shell/browser/net/proxying_url_loader_factory.cc @@ -55,9 +55,9 @@ ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest( proxied_loader_receiver_(this, std::move(loader_receiver)), target_client_(std::move(client)), current_response_(network::mojom::URLResponseHead::New()), - // Always use "extraHeaders" mode to be compatible with old APIs, except - // when the |request_id_| is zero, which is not supported in Chromium and - // only happens in Electron when the request is started from net module. + // Always use "extraHeaders" mode to be compatible with old APIs. + // A non-zero request ID is required to use the TrustedHeaderClient path + // which allows webRequest to modify headers like Proxy-Authorization. has_any_extra_headers_listeners_(network_service_request_id != 0) { // If there is a client error, clean up the request. target_client_.set_disconnect_handler(base::BindOnce( diff --git a/shell/common/api/electron_api_url_loader.cc b/shell/common/api/electron_api_url_loader.cc index 7e7a7d04ef..0ce6a8ca24 100644 --- a/shell/common/api/electron_api_url_loader.cc +++ b/shell/common/api/electron_api_url_loader.cc @@ -17,6 +17,7 @@ #include "base/memory/raw_ptr.h" #include "base/notreached.h" #include "base/sequence_checker.h" +#include "content/public/browser/global_request_id.h" #include "gin/object_template_builder.h" #include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/system/data_pipe_producer.h" @@ -375,6 +376,13 @@ void SimpleURLLoaderWrapper::Start() { loader_->SetAllowHttpErrorResults(true); loader_->SetURLLoaderFactoryOptions(request_options_); + // Set a non-zero request ID so that the request can use the + // TrustedHeaderClient code path for webRequest header modifications. + // See proxying_url_loader_factory.cc for details. + if (electron::IsBrowserProcess()) { + loader_->SetRequestID( + content::GlobalRequestID::MakeBrowserInitiated().request_id); + } loader_->SetOnResponseStartedCallback(base::BindOnce( &SimpleURLLoaderWrapper::OnResponseStarted, weak_factory_.GetWeakPtr())); loader_->SetOnRedirectCallback(base::BindRepeating( diff --git a/spec/api-web-request-spec.ts b/spec/api-web-request-spec.ts index 9000b2d2b1..6160129cfa 100644 --- a/spec/api-web-request-spec.ts +++ b/spec/api-web-request-spec.ts @@ -1,4 +1,4 @@ -import { ipcMain, protocol, session, WebContents, webContents } from 'electron/main'; +import { ipcMain, net, protocol, session, WebContents, webContents } from 'electron/main'; import { expect } from 'chai'; import * as WebSocket from 'ws'; @@ -455,6 +455,35 @@ describe('webRequest module', () => { })); expect(onSendHeadersCalled).to.be.true(); }); + + it('can inject Proxy-Authorization header for net module requests', async () => { + // Proxy-Authorization is normally rejected by Chromium's network service + // for security reasons. However, for Electron's trusted net module, + // webRequest.onBeforeSendHeaders should be able to inject it via the + // TrustedHeaderClient code path. + const proxyAuthValue = 'Basic test-credentials'; + let receivedProxyAuth: string | undefined; + + const server = http.createServer((req, res) => { + receivedProxyAuth = req.headers['proxy-authorization']; + res.end('ok'); + }); + const { url: serverUrl } = await listen(server); + + try { + ses.webRequest.onBeforeSendHeaders((details, callback) => { + const requestHeaders = details.requestHeaders; + requestHeaders['Proxy-Authorization'] = proxyAuthValue; + callback({ requestHeaders }); + }); + + const response = await net.fetch(serverUrl, { bypassCustomProtocolHandlers: true }); + expect(response.ok).to.be.true(); + expect(receivedProxyAuth).to.equal(proxyAuthValue); + } finally { + server.close(); + } + }); }); describe('webRequest.onSendHeaders', () => {