Repository: electron/electron
Pull request: fix: `webRequest.onBeforeSendHeaders` not being able to modify reserved headers

Backport of #49226

See that PR for details.


Notes: Requests sent via `net` are now capable of having their headers modified to use reserved headers via `webRequest`

Changed files:
filename: shell/browser/net/proxying_url_loader_factory.cc
patch: @@ -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(

filename: shell/common/api/electron_api_url_loader.cc
patch: @@ -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(

filename: spec/api-web-request-spec.ts
patch: @@ -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', () => {