[WAR, DNR] Fix unsafe redirect error to web accessible resource

Error was observed on web pages that had a service worker for fetching
subresources. This caused the page to use the SharedURLLoaderFactory
which never bypasses renderer level redirect checks.

Combine that with the fact that the request's initiator was not passed
down into the renderer's redirect check + a redirect to an extension's
Web Accessible Resource that required the initiator to check if it was
a safe redirect, that led to the UNSAFE_REDIRECT error observed.

Fix involved adding the request's initiator to RedirectInfo which can
then be used in the renderer.

Bug: 375395102
Change-Id: I84a526c165847cfe390978d7133022aee25dd303
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6580522
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Reviewed-by: Michael Thiessen <mthiesse@chromium.org>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Commit-Queue: Kelvin Jiang <kelvinjiang@chromium.org>
Reviewed-by: Solomon Kinard <solomonkinard@chromium.org>
Reviewed-by: Kenichi Ishibashi <bashi@chromium.org>
Reviewed-by: Mustafa Emre Acer <meacer@chromium.org>
Reviewed-by: Devlin Cronin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1479568}
diff --git a/chrome/browser/extensions/web_accessible_resources_browsertest.cc b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
index c84274ac..972b4f8 100644
--- a/chrome/browser/extensions/web_accessible_resources_browsertest.cc
+++ b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
@@ -595,6 +595,70 @@
 }
 
 #if !BUILDFLAG(IS_ANDROID)
+
+// TODO(crbug.com/425708956): Enable this test for desktop Android.
+class WebAccessibleResourcesServiceWorkerBrowserTest
+    : public WebAccessibleResourcesBrowserTest {
+ public:
+  WebAccessibleResourcesServiceWorkerBrowserTest() {
+    UseHttpsTestServer();
+
+    // Add any host names used by tests from this class to the test server's SSL
+    // config since tests will navigate there.
+    net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+    cert_config.dns_names = {"example.com"};
+    embedded_test_server()->SetSSLConfig(cert_config);
+  }
+
+  ~WebAccessibleResourcesServiceWorkerBrowserTest() override = default;
+  WebAccessibleResourcesServiceWorkerBrowserTest(
+      const WebAccessibleResourcesServiceWorkerBrowserTest&) = delete;
+  WebAccessibleResourcesServiceWorkerBrowserTest& operator=(
+      const WebAccessibleResourcesServiceWorkerBrowserTest&) = delete;
+
+ protected:
+  void RegisterServiceWorker(const std::string& host_name,
+                             const std::string& worker_path,
+                             const std::optional<std::string>& scope) {
+    GURL url = embedded_test_server()->GetURL(
+        host_name, "/service_worker/create_service_worker.html");
+    EXPECT_TRUE(NavigateToURL(url));
+    std::string script = content::JsReplace("register($1, $2);", worker_path,
+                                            scope ? *scope : std::string());
+    EXPECT_EQ("DONE", EvalJs(GetActiveWebContents(), script));
+  }
+};
+
+// Test that DNR redirects to the extension's web accessible resource work when
+// the page has a service worker. Unlike the WebAccessibleResourcesBrowserTest
+// version, the service worker causes a renderer level redirect check for the
+// web accessible resource.
+// Regression test for crbug.com/375395102.
+IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesServiceWorkerBrowserTest,
+                       DNRRedirect) {
+  // Register a service worker and navigate to a page it controls.
+  RegisterServiceWorker("example.com", "fetch_event_pass_through.js",
+                        std::nullopt);
+  EXPECT_TRUE(NavigateToURL(embedded_test_server()->GetURL(
+      "example.com", "/service_worker/fetch_from_page.html")));
+
+  const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
+      "web_accessible_resources/dnr/redirect_with_initiator"));
+  ASSERT_TRUE(extension);
+
+  // Fetch the english page. It should be redirected to the extension's web
+  // accessible resource. Note: we "lose" the service worker if we attempt to
+  // navigate to the page instead, so a fetch is used here.
+  auto result =
+      EvalJs(GetActiveWebContents(), "fetch_from_page('/english_page.html');");
+
+  std::string expected_content =
+      "// Redirect with initiator's web accessible resource!";
+  EXPECT_TRUE(result.ExtractString().find(expected_content) !=
+              std::string::npos)
+      << expected_content << " not found in " << result.ExtractString();
+}
+
 // TODO(crbug.com/390687767): Port to desktop Android. Currently the redirect
 // doesn't happen.
 
diff --git a/chrome/browser/ssl/https_upgrades_interceptor.cc b/chrome/browser/ssl/https_upgrades_interceptor.cc
index 990c0b63..ee0f775 100644
--- a/chrome/browser/ssl/https_upgrades_interceptor.cc
+++ b/chrome/browser/ssl/https_upgrades_interceptor.cc
@@ -119,7 +119,7 @@
           ? net::RedirectInfo::FirstPartyURLPolicy::UPDATE_URL_ON_REDIRECT
           : net::RedirectInfo::FirstPartyURLPolicy::NEVER_CHANGE_URL,
       request.referrer_policy, request.referrer.spec(),
-      net::HTTP_TEMPORARY_REDIRECT, new_url,
+      request.request_initiator, net::HTTP_TEMPORARY_REDIRECT, new_url,
       /*referrer_policy_header=*/std::nullopt,
       /*insecure_scheme_was_upgraded=*/false);
   return redirect_info;
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 804a696..e5cde87 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -1584,8 +1584,10 @@
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS_CORE)
 }
 
-bool ChromeContentRendererClient::IsSafeRedirectTarget(const GURL& upstream_url,
-                                                       const GURL& target_url) {
+bool ChromeContentRendererClient::IsSafeRedirectTarget(
+    const GURL& upstream_url,
+    const GURL& target_url,
+    const std::optional<url::Origin>& request_initiator) {
 #if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
   if (target_url.SchemeIs(extensions::kExtensionScheme)) {
     const extensions::Extension* extension =
@@ -1594,10 +1596,8 @@
     if (!extension) {
       return false;
     }
-    // TODO(solomonkinard): Use initiator_origin and add tests.
     if (extensions::WebAccessibleResourcesInfo::IsResourceWebAccessibleRedirect(
-            extension, target_url, /*initiator_origin=*/std::nullopt,
-            upstream_url)) {
+            extension, target_url, request_initiator, upstream_url)) {
       return true;
     }
     return extension->guid() == upstream_url.host();
diff --git a/chrome/renderer/chrome_content_renderer_client.h b/chrome/renderer/chrome_content_renderer_client.h
index d0980b2..ea0553c 100644
--- a/chrome/renderer/chrome_content_renderer_client.h
+++ b/chrome/renderer/chrome_content_renderer_client.h
@@ -221,7 +221,10 @@
       blink::URLLoaderThrottleProviderType provider_type) override;
   blink::WebFrame* FindFrame(blink::WebLocalFrame* relative_to_frame,
                              const std::string& name) override;
-  bool IsSafeRedirectTarget(const GURL& from_url, const GURL& to_url) override;
+  bool IsSafeRedirectTarget(
+      const GURL& from_url,
+      const GURL& to_url,
+      const std::optional<url::Origin>& request_initiator) override;
   void DidSetUserAgent(const std::string& user_agent) override;
   void AppendContentSecurityPolicy(
       const blink::WebURL& url,
diff --git a/chrome/test/data/extensions/web_accessible_resources/dnr/redirect_with_initiator/manifest.json b/chrome/test/data/extensions/web_accessible_resources/dnr/redirect_with_initiator/manifest.json
new file mode 100644
index 0000000..8f82463
--- /dev/null
+++ b/chrome/test/data/extensions/web_accessible_resources/dnr/redirect_with_initiator/manifest.json
@@ -0,0 +1,22 @@
+{
+  "name": "DNR redirect extension to WAR with an initiator condition",
+  "version": "0.1",
+  "manifest_version": 3,
+  "permissions": ["declarativeNetRequest"],
+  "host_permissions": ["<all_urls>"],
+  "web_accessible_resources": [
+    {
+      "resources": ["war.js"],
+      "matches": ["<all_urls>"]
+    }
+  ],
+  "declarative_net_request": {
+    "rule_resources": [
+      {
+        "id": "ruleset_1",
+        "path": "rules_1.json",
+        "enabled": true
+      }
+    ]
+  }
+}
diff --git a/chrome/test/data/extensions/web_accessible_resources/dnr/redirect_with_initiator/rules_1.json b/chrome/test/data/extensions/web_accessible_resources/dnr/redirect_with_initiator/rules_1.json
new file mode 100644
index 0000000..7b1ca53
--- /dev/null
+++ b/chrome/test/data/extensions/web_accessible_resources/dnr/redirect_with_initiator/rules_1.json
@@ -0,0 +1,17 @@
+[
+  {
+    "action": {
+      "redirect": {
+        "extensionPath": "/war.js"
+      },
+      "type": "redirect"
+    },
+    "condition": {
+      "regexFilter": ".*/english_page.html",
+      "initiatorDomains": [
+        "example.com"
+      ]
+    },
+    "id": 1
+  }
+]
diff --git a/chrome/test/data/extensions/web_accessible_resources/dnr/redirect_with_initiator/war.js b/chrome/test/data/extensions/web_accessible_resources/dnr/redirect_with_initiator/war.js
new file mode 100644
index 0000000..3fa69242f
--- /dev/null
+++ b/chrome/test/data/extensions/web_accessible_resources/dnr/redirect_with_initiator/war.js
@@ -0,0 +1,5 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Redirect with initiator's web accessible resource!
diff --git a/components/navigation_interception/intercept_navigation_delegate.cc b/components/navigation_interception/intercept_navigation_delegate.cc
index 4ecedcb..5eac5f0 100644
--- a/components/navigation_interception/intercept_navigation_delegate.cc
+++ b/components/navigation_interception/intercept_navigation_delegate.cc
@@ -84,7 +84,8 @@
         net::RedirectInfo::ComputeRedirectInfo(
             request_.method, request_.url, request_.site_for_cookies,
             first_party_url_policy, request_.referrer_policy,
-            request_.referrer.spec(), response_code, *url, std::nullopt,
+            request_.referrer.spec(), request_.request_initiator, response_code,
+            *url, std::nullopt,
             /*insecure_scheme_was_upgraded=*/false,
             /*copy_fragment=*/false),
         std::move(response_head));
diff --git a/content/browser/devtools/devtools_url_loader_interceptor.cc b/content/browser/devtools/devtools_url_loader_interceptor.cc
index f243e7ee..6845e6a 100644
--- a/content/browser/devtools/devtools_url_loader_interceptor.cc
+++ b/content/browser/devtools/devtools_url_loader_interceptor.cc
@@ -1341,7 +1341,8 @@
       net::RedirectInfo::ComputeRedirectInfo(
           request.method, request.url, request.site_for_cookies,
           first_party_url_policy, request.referrer_policy,
-          request.referrer.spec(), headers.response_code(), redirect_url,
+          request.referrer.spec(), request.request_initiator,
+          headers.response_code(), redirect_url,
           net::RedirectUtil::GetReferrerPolicyHeader(&headers),
           false /* insecure_scheme_was_upgraded */, true /* copy_fragment */));
 
diff --git a/content/browser/web_package/signed_exchange_utils.cc b/content/browser/web_package/signed_exchange_utils.cc
index 4b7d75e..356cd5a 100644
--- a/content/browser/web_package/signed_exchange_utils.cc
+++ b/content/browser/web_package/signed_exchange_utils.cc
@@ -208,8 +208,8 @@
       outer_request.update_first_party_url_on_redirect
           ? net::RedirectInfo::FirstPartyURLPolicy::UPDATE_URL_ON_REDIRECT
           : net::RedirectInfo::FirstPartyURLPolicy::NEVER_CHANGE_URL,
-      outer_request.referrer_policy, outer_request.referrer.spec(), 303,
-      new_url,
+      outer_request.referrer_policy, outer_request.referrer.spec(),
+      outer_request.request_initiator, 303, new_url,
       net::RedirectUtil::GetReferrerPolicyHeader(outer_response.headers.get()),
       false /* insecure_scheme_was_upgraded */, true /* copy_fragment */,
       is_fallback_redirect);
diff --git a/content/public/renderer/content_renderer_client.cc b/content/public/renderer/content_renderer_client.cc
index 05087b20..a87fd36 100644
--- a/content/public/renderer/content_renderer_client.cc
+++ b/content/public/renderer/content_renderer_client.cc
@@ -295,8 +295,10 @@
   return nullptr;
 }
 
-bool ContentRendererClient::IsSafeRedirectTarget(const GURL& from_url,
-                                                 const GURL& to_url) {
+bool ContentRendererClient::IsSafeRedirectTarget(
+    const GURL& from_url,
+    const GURL& to_url,
+    const std::optional<url::Origin>& request_initiator) {
   return true;
 }
 
diff --git a/content/public/renderer/content_renderer_client.h b/content/public/renderer/content_renderer_client.h
index 6d5859d..ef58cfe 100644
--- a/content/public/renderer/content_renderer_client.h
+++ b/content/public/renderer/content_renderer_client.h
@@ -429,8 +429,12 @@
   virtual blink::WebFrame* FindFrame(blink::WebLocalFrame* relative_to_frame,
                                      const std::string& name);
 
-  // Returns true only if it's safe to redirect `from_url` to `to_url`.
-  virtual bool IsSafeRedirectTarget(const GURL& from_url, const GURL& to_url);
+  // Returns true only if it's safe to redirect `from_url` to `to_url`. May also
+  // check `request_initiator` depending on `to_url`.
+  virtual bool IsSafeRedirectTarget(
+      const GURL& from_url,
+      const GURL& to_url,
+      const std::optional<url::Origin>& request_initiator);
 
   // The user agent string is given from the browser process. This is called at
   // most once.
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
index e54e804..e81aa09 100644
--- a/content/renderer/renderer_blink_platform_impl.cc
+++ b/content/renderer/renderer_blink_platform_impl.cc
@@ -355,12 +355,14 @@
   return render_thread->GetUserAgentMetadata();
 }
 
-bool RendererBlinkPlatformImpl::IsRedirectSafe(const GURL& from_url,
-                                               const GURL& to_url) {
+bool RendererBlinkPlatformImpl::IsRedirectSafe(
+    const GURL& from_url,
+    const GURL& to_url,
+    const std::optional<url::Origin>& request_initiator) {
   return IsSafeRedirectTarget(from_url, to_url) &&
          (!GetContentClient()->renderer() ||  // null in unit tests.
-          GetContentClient()->renderer()->IsSafeRedirectTarget(from_url,
-                                                               to_url));
+          GetContentClient()->renderer()->IsSafeRedirectTarget(
+              from_url, to_url, request_initiator));
 }
 
 void RendererBlinkPlatformImpl::AppendVariationsThrottles(
diff --git a/content/renderer/renderer_blink_platform_impl.h b/content/renderer/renderer_blink_platform_impl.h
index b4fc08d..c2c2b53 100644
--- a/content/renderer/renderer_blink_platform_impl.h
+++ b/content/renderer/renderer_blink_platform_impl.h
@@ -90,7 +90,10 @@
                                   uint64_t salt) override;
   blink::WebString UserAgent() override;
   blink::UserAgentMetadata UserAgentMetadata() override;
-  bool IsRedirectSafe(const GURL& from_url, const GURL& to_url) override;
+  bool IsRedirectSafe(
+      const GURL& from_url,
+      const GURL& to_url,
+      const std::optional<url::Origin>& request_initiator) override;
   void AppendVariationsThrottles(
       const url::Origin& top_origin,
       std::vector<std::unique_ptr<blink::URLLoaderThrottle>>* throttles)
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
index 8f02078..e9a9f23 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
@@ -122,7 +122,8 @@
           ? net::RedirectInfo::FirstPartyURLPolicy::UPDATE_URL_ON_REDIRECT
           : net::RedirectInfo::FirstPartyURLPolicy::NEVER_CHANGE_URL,
       original_request.referrer_policy, original_request.referrer.spec(),
-      response_code, new_url, referrer_policy_header,
+      original_request.request_initiator, response_code, new_url,
+      referrer_policy_header,
       /*insecure_scheme_was_upgraded=*/false, /*copy_fragment=*/false,
       /*is_signed_exchange_fallback_redirect=*/false);
 }
@@ -690,11 +691,14 @@
       kInternalRedirectStatusCode, redirect_url_.spec().c_str());
 
   // Cross-origin requests need to modify the Origin header to 'null'. Since
-  // CorsURLLoader sets |request_initiator| to the Origin request header in
-  // NetworkService, we need to modify |request_initiator| here to craft the
+  // CorsURLLoader sets `request_initiator` to the Origin request header in
+  // NetworkService, we need to modify `request_initiator` here to craft the
   // Origin header indirectly.
   // Following checks implement the step 10 of "4.4. HTTP-redirect fetch",
   // https://fetch.spec.whatwg.org/#http-redirect-fetch
+  // Note: The original initiator is still recorded in `redirect_info`. This is
+  // intentional as it may be used inside the renderer to check if a redirect to
+  // an extension's Web Accessible Resource is safe.
   if (request_.request_initiator &&
       (!url::IsSameOriginWith(redirect_url_, request_.url) &&
        !request_.request_initiator->IsSameOriginWith(request_.url))) {
diff --git a/net/url_request/redirect_info.cc b/net/url_request/redirect_info.cc
index becf2a4..037df28 100644
--- a/net/url_request/redirect_info.cc
+++ b/net/url_request/redirect_info.cc
@@ -65,6 +65,7 @@
     RedirectInfo::FirstPartyURLPolicy original_first_party_url_policy,
     ReferrerPolicy original_referrer_policy,
     const std::string& original_referrer,
+    const std::optional<url::Origin>& original_initiator,
     int http_status_code,
     const GURL& new_location,
     const std::optional<std::string>& referrer_policy_header,
@@ -73,6 +74,7 @@
     bool is_signed_exchange_fallback_redirect) {
   RedirectInfo redirect_info;
 
+  redirect_info.original_initiator = original_initiator;
   redirect_info.status_code = http_status_code;
 
   // The request method may change, depending on the status code.
diff --git a/net/url_request/redirect_info.h b/net/url_request/redirect_info.h
index 810eaf97..49fe7a9 100644
--- a/net/url_request/redirect_info.h
+++ b/net/url_request/redirect_info.h
@@ -13,6 +13,7 @@
 #include "net/cookies/site_for_cookies.h"
 #include "net/url_request/referrer_policy.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 namespace net {
 
@@ -42,6 +43,7 @@
       FirstPartyURLPolicy original_first_party_url_policy,
       ReferrerPolicy original_referrer_policy,
       const std::string& original_referrer,
+      const std::optional<url::Origin>& original_initiator,
       // The HTTP status code of the redirect response.
       int http_status_code,
       // The new location URL of the redirect response.
@@ -58,6 +60,10 @@
       // Whether the redirect is caused by a failure of signed exchange loading.
       bool is_signed_exchange_fallback_redirect = false);
 
+  // The original request's initiator. This is used for some checks if a
+  // redirect is safe.
+  std::optional<url::Origin> original_initiator;
+
   // The status code for the redirect response. This is almost redundant with
   // the response headers, but some URLRequestJobs emit redirects without
   // headers.
diff --git a/net/url_request/redirect_info_unittest.cc b/net/url_request/redirect_info_unittest.cc
index ce10b7a3..d1ea43f8a 100644
--- a/net/url_request/redirect_info_unittest.cc
+++ b/net/url_request/redirect_info_unittest.cc
@@ -52,8 +52,9 @@
     RedirectInfo redirect_info = RedirectInfo::ComputeRedirectInfo(
         test.original_method, kOriginalUrl, kOriginalSiteForCookies,
         kOriginalFirstPartyUrlPolicy, kOriginalReferrerPolicy,
-        kOriginalReferrer, test.http_status_code, kNewLocation,
-        std::nullopt /* referrer_policy_header */, kInsecureSchemeWasUpgraded,
+        kOriginalReferrer, /*original_initiator=*/std::nullopt,
+        test.http_status_code, kNewLocation,
+        /*referrer_policy_header=*/std::nullopt, kInsecureSchemeWasUpgraded,
         kCopyFragment);
 
     EXPECT_EQ(test.expected_new_method, redirect_info.new_method);
@@ -103,8 +104,9 @@
     RedirectInfo redirect_info = RedirectInfo::ComputeRedirectInfo(
         kOriginalMethod, GURL(test.original_url), kOriginalSiteForCookies,
         kOriginalFirstPartyUrlPolicy, kOriginalReferrerPolicy,
-        kOriginalReferrer, kHttpStatusCode, GURL(test.new_location),
-        std::nullopt /* referrer_policy_header */, kInsecureSchemeWasUpgraded,
+        kOriginalReferrer, /*original_initiator=*/std::nullopt, kHttpStatusCode,
+        GURL(test.new_location),
+        /*referrer_policy_header=*/std::nullopt, kInsecureSchemeWasUpgraded,
         test.copy_fragment);
 
     EXPECT_EQ(GURL(test.expected_new_url), redirect_info.new_url);
@@ -142,8 +144,9 @@
     RedirectInfo redirect_info = RedirectInfo::ComputeRedirectInfo(
         kOriginalMethod, kOriginalUrl, kOriginalSiteForCookies,
         test.original_first_party_url_policy, kOriginalReferrerPolicy,
-        kOriginalReferrer, kHttpStatusCode, kNewLocation,
-        std::nullopt /* referrer_policy_header */, kInsecureSchemeWasUpgraded,
+        kOriginalReferrer, /*original_initiator=*/std::nullopt, kHttpStatusCode,
+        kNewLocation,
+        /*referrer_policy_header=*/std::nullopt, kInsecureSchemeWasUpgraded,
         kCopyFragment);
 
     EXPECT_TRUE(redirect_info.new_site_for_cookies.IsEquivalent(
@@ -462,7 +465,8 @@
     RedirectInfo redirect_info = RedirectInfo::ComputeRedirectInfo(
         kOriginalMethod, original_url, kOriginalSiteForCookies,
         kOriginalFirstPartyUrlPolicy, test.original_referrer_policy,
-        test.original_referrer, response_headers->response_code(), new_location,
+        test.original_referrer, /*original_initiator=*/std::nullopt,
+        response_headers->response_code(), new_location,
         RedirectUtil::GetReferrerPolicyHeader(response_headers.get()),
         kInsecureSchemeWasUpgraded, kCopyFragment);
 
diff --git a/net/url_request/url_request_job.cc b/net/url_request/url_request_job.cc
index 8bdef06e..8b0de816 100644
--- a/net/url_request/url_request_job.cc
+++ b/net/url_request/url_request_job.cc
@@ -468,7 +468,8 @@
     RedirectInfo redirect_info = RedirectInfo::ComputeRedirectInfo(
         request_->method(), request_->url(), request_->site_for_cookies(),
         request_->first_party_url_policy(), request_->referrer_policy(),
-        request_->referrer(), http_status_code, new_location,
+        request_->referrer(), request_->initiator(), http_status_code,
+        new_location,
         net::RedirectUtil::GetReferrerPolicyHeader(
             request_->response_headers()),
         insecure_scheme_was_upgraded, CopyFragmentOnRedirect(new_location));
diff --git a/services/network/prefetch_url_loader_client_unittest.cc b/services/network/prefetch_url_loader_client_unittest.cc
index ed1524ab..f1a6e89 100644
--- a/services/network/prefetch_url_loader_client_unittest.cc
+++ b/services/network/prefetch_url_loader_client_unittest.cc
@@ -160,7 +160,8 @@
       "GET", TestURL(), TestIsolationInfo().site_for_cookies(),
       RedirectInfo::FirstPartyURLPolicy::NEVER_CHANGE_URL,
       net::ReferrerPolicy::CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE,
-      TestOrigin().Serialize(), 301, GURL(kRedirectTo), std::nullopt, false);
+      TestOrigin().Serialize(), /*original_initiator=*/std::nullopt, 301,
+      GURL(kRedirectTo), std::nullopt, false);
 }
 
 // Returns true if two SiteForCookies objects match.
diff --git a/services/network/public/cpp/net_ipc_param_traits.h b/services/network/public/cpp/net_ipc_param_traits.h
index aeb766e3..4d12481d 100644
--- a/services/network/public/cpp/net_ipc_param_traits.h
+++ b/services/network/public/cpp/net_ipc_param_traits.h
@@ -268,6 +268,7 @@
 IPC_STRUCT_TRAITS_END()
 
 IPC_STRUCT_TRAITS_BEGIN(net::RedirectInfo)
+  IPC_STRUCT_TRAITS_MEMBER(original_initiator)
   IPC_STRUCT_TRAITS_MEMBER(status_code)
   IPC_STRUCT_TRAITS_MEMBER(new_method)
   IPC_STRUCT_TRAITS_MEMBER(new_url)
diff --git a/third_party/blink/common/loader/throttling_url_loader.cc b/third_party/blink/common/loader/throttling_url_loader.cc
index 7f0fed7..3649b2e 100644
--- a/third_party/blink/common/loader/throttling_url_loader.cc
+++ b/third_party/blink/common/loader/throttling_url_loader.cc
@@ -502,6 +502,7 @@
         start_info_->url_request.site_for_cookies, first_party_url_policy,
         start_info_->url_request.referrer_policy,
         start_info_->url_request.referrer.spec(),
+        start_info_->url_request.request_initiator,
         // Use status code 307 to preserve the method, so POST requests work.
         net::HTTP_TEMPORARY_REDIRECT, throttle_will_start_redirect_url_,
         std::nullopt, false, false, false);
diff --git a/third_party/blink/common/service_worker/service_worker_loader_helpers.cc b/third_party/blink/common/service_worker/service_worker_loader_helpers.cc
index 9265c53..350507c 100644
--- a/third_party/blink/common/service_worker/service_worker_loader_helpers.cc
+++ b/third_party/blink/common/service_worker/service_worker_loader_helpers.cc
@@ -158,6 +158,7 @@
       original_request.site_for_cookies, first_party_url_policy,
       original_request.referrer_policy,
       original_request.referrer.GetAsReferrer().spec(),
+      original_request.request_initiator,
       response_head.headers->response_code(),
       original_request.url.Resolve(new_location),
       net::RedirectUtil::GetReferrerPolicyHeader(response_head.headers.get()),
diff --git a/third_party/blink/public/platform/platform.h b/third_party/blink/public/platform/platform.h
index eee2a8d..dfc5c935 100644
--- a/third_party/blink/public/platform/platform.h
+++ b/third_party/blink/public/platform/platform.h
@@ -290,7 +290,10 @@
   }
 
   // Determines whether it is safe to redirect from |from_url| to |to_url|.
-  virtual bool IsRedirectSafe(const GURL& from_url, const GURL& to_url) {
+  virtual bool IsRedirectSafe(
+      const GURL& from_url,
+      const GURL& to_url,
+      const std::optional<url::Origin>& request_initiator) {
     return false;
   }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/background_url_loader_unittest.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/background_url_loader_unittest.cc
index d38ba45..3236e07 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/background_url_loader_unittest.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/background_url_loader_unittest.cc
@@ -524,7 +524,10 @@
  private:
   class TestPlatformForRedirects final : public TestingPlatformSupport {
    public:
-    bool IsRedirectSafe(const GURL& from_url, const GURL& to_url) override {
+    bool IsRedirectSafe(
+        const GURL& from_url,
+        const GURL& to_url,
+        const std::optional<url::Origin>& request_initiator) override {
       return true;
     }
   };
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client.cc
index 7d29a566..4685546 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client.cc
@@ -390,9 +390,11 @@
     OnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED));
     return;
   }
+
   if (!bypass_redirect_checks_ &&
       !Platform::Current()->IsRedirectSafe(GURL(last_loaded_url_),
-                                           redirect_info.new_url)) {
+                                           redirect_info.new_url,
+                                           redirect_info.original_initiator)) {
     OnComplete(network::URLLoaderCompletionStatus(net::ERR_UNSAFE_REDIRECT));
     return;
   }
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client_unittest.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client_unittest.cc
index b7b0b5b4..ece0e21 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client_unittest.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client_unittest.cc
@@ -248,7 +248,10 @@
 
   class TestPlatform final : public TestingPlatformSupport {
    public:
-    bool IsRedirectSafe(const GURL& from_url, const GURL& to_url) override {
+    bool IsRedirectSafe(
+        const GURL& from_url,
+        const GURL& to_url,
+        const std::optional<url::Origin>& request_initiator) override {
       return true;
     }
   };
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender_unittest.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender_unittest.cc
index 6deb1e1..b1b0649 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender_unittest.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender_unittest.cc
@@ -129,7 +129,10 @@
 
 class TestPlatformForRedirects final : public TestingPlatformSupport {
  public:
-  bool IsRedirectSafe(const GURL& from_url, const GURL& to_url) override {
+  bool IsRedirectSafe(
+      const GURL& from_url,
+      const GURL& to_url,
+      const std::optional<url::Origin>& request_initiator) override {
     return true;
   }
 };