Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Isolated Web Apps
DescriptionInsufficient validation of untrusted input in Isolated Web Apps
ComponentIsolated Web Apps
Bug ClassLogic Error
Tracker501693236
Fix commit0033db1d8f04 (chromium/src) +575/-44
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
chrome/browser/web_applications/isolated_web_apps/update/isolated_web_app_update_check_and_prepare_task.cc
modified

Files Changed

  • chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.cc
  • chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.cc
  • chrome/browser/web_applications/DEPS
  • chrome/browser/web_applications/isolated_web_apps/install/isolated_web_app_dev_install_manager.cc
  • chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_installer.cc
  • chrome/browser/web_applications/isolated_web_apps/update/isolated_web_app_update_check_and_prepare_task.cc
  • chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher.cc
From 0033db1d8f042aa1a010e38a569d2af64c577c16 Mon Sep 17 00:00:00 2001
From: Olga Korokhina <[email protected]>
Date: Mon, 22 Jun 2026 12:32:14 -0700
Subject: [PATCH] Implement secure URL Redirect lookup for updates and installation

This CL implements strict Local Network Access (LNA) enforcement for
Isolated Web App updates and installation to prevent unauthorized access
to local resources (e.g., via SSRF redirects).

One pager on CL logic
https://docs.google.com/document/d/1im12dvBPdMmaIA_Dh6xQD4tsce5UA78bNhW8rn7i3-o/edit?resourcekey=0-_cOgsPu-CmrJc_PKaV0EhQ&tab=t.0#heading=h.x3cvieqymmv

Bug: 501693236
Change-Id: I2be0dcf8a9a26ffbc36f57c8daa45c9a59e222a1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7912822
Reviewed-by: Andrew Rayskiy <[email protected]>
Reviewed-by: Daniel Murphy <[email protected]>
Reviewed-by: Chris Thompson <[email protected]>
Commit-Queue: Olga Korokhina <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1650497}
---

diff --git a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.cc b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.cc
index f87ae368..1ad62dd 100644
--- a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.cc
+++ b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.cc
@@ -464,7 +464,8 @@
   update_manifest_fetcher_ = std::make_unique<UpdateManifestFetcher>(
       update_manifest_url.value(), kUpdateManifestFetchTrafficAnnotation,
       profile_->GetDefaultStoragePartition()
-          ->GetURLLoaderFactoryForBrowserProcess());
+          ->GetURLLoaderFactoryForBrowserProcess(),
+      profile_->GetDefaultStoragePartition()->GetNetworkContext());
 
   update_manifest_fetcher_->FetchUpdateManifest(base::BindOnce(
       &IsolatedWebAppInstallerViewController::OnUpdateManifestFetched,
diff --git a/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.cc b/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.cc
index 3897c1413..648f93d 100644
--- a/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.cc
+++ b/chrome/browser/ui/webui/web_app_internals/iwa_internals_handler.cc
@@ -16,6 +16,7 @@
 #include "base/types/expected_macros.h"
 #include "base/types/optional_util.h"
 #include "chrome/browser/file_select_helper.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom.h"
 #include "chrome/browser/web_applications/isolated_web_apps/commands/install_isolated_web_app_command.h"
 #include "chrome/browser/web_applications/isolated_web_apps/install/isolated_web_app_dev_install_manager.h"
@@ -39,6 +40,7 @@
 #include "components/webapps/isolated_web_apps/types/iwa_version.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "third_party/abseil-cpp/absl/functional/overload.h"
@@ -294,7 +296,9 @@
 
   auto fetcher = std::make_unique<UpdateManifestFetcher>(
       update_manifest_url, kUpdateManifestFetchAnnotation,
-      profile()->GetURLLoaderFactory());
+      profile()->GetURLLoaderFactory(),
+      profile()->GetDefaultStoragePartition()->GetNetworkContext());
+
   auto* fetcher_ptr = fetcher.get();
 
   base::OnceClosure fetcher_keep_alive =
diff --git a/chrome/browser/web_applications/DEPS b/chrome/browser/web_applications/DEPS
index 1f7cf78..c681faf 100644
--- a/chrome/browser/web_applications/DEPS
+++ b/chrome/browser/web_applications/DEPS
@@ -12,6 +12,7 @@
   "+mojo/core/embedder",
   "+mojo/public/cpp/bindings",
   "+services/network/public/cpp",
+  "+services/network/public/mojom",
   "+third_party/blink/public/common",
   "+third_party/fuzztest",
   "+third_party/liburlpattern",
diff --git a/chrome/browser/web_applications/isolated_web_apps/install/isolated_web_app_dev_install_manager.cc b/chrome/browser/web_applications/isolated_web_apps/install/isolated_web_app_dev_install_manager.cc
index 0b600c17..d603478a 100644
--- a/chrome/browser/web_applications/isolated_web_apps/install/isolated_web_app_dev_install_manager.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/install/isolated_web_app_dev_install_manager.cc
@@ -46,6 +46,7 @@
 #include "components/webapps/isolated_web_apps/types/source.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/isolated_web_apps_policy.h"
+#include "content/public/browser/storage_partition.h"
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "third_party/abseil-cpp/absl/functional/overload.h"
@@ -585,8 +586,9 @@
     std::optional<web_package::SignedWebBundleId> expected_bundle_id,
     ScopedTempWebBundleFile bundle) {
   base::FilePath path = bundle.path();
-  auto downloader = std::make_unique<IsolatedWebAppDownloader>(
-      profile()->GetURLLoaderFactory());
+  auto downloader = IsolatedWebAppDownloader::Create(
+      profile()->GetURLLoaderFactory(),
+      profile()->GetDefaultStoragePartition()->GetNetworkContext());
   auto* downloader_ptr = downloader.get();
   base::OnceClosure downloader_keep_alive =
       base::DoNothingWithBoundArgs(std::move(downloader));
diff --git a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_installer.cc b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_installer.cc
index 1633f096..ad65f31 100644
--- a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_installer.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_installer.cc
@@ -14,6 +14,7 @@
 #include "base/strings/to_string.h"
 #include "base/types/expected_macros.h"
 #include "base/types/optional_util.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/callback_utils.h"
 #include "chrome/browser/web_applications/isolated_web_apps/commands/install_isolated_web_app_command.h"
 #include "chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_external_install_options.h"
@@ -24,6 +25,7 @@
 #include "chrome/common/chrome_features.h"
 #include "components/webapps/isolated_web_apps/download/bundle_downloader.h"
 #include "components/webapps/isolated_web_apps/types/source.h"
+#include "content/public/browser/storage_partition.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
@@ -317,7 +319,8 @@
 
   update_manifest_fetcher_ = std::make_unique<UpdateManifestFetcher>(
       install_options_.update_manifest_url(),
-      kUpdateManifestFetchTrafficAnnotation, url_loader_factory_);
+      kUpdateManifestFetchTrafficAnnotation, url_loader_factory_,
+      provider_->profile()->GetDefaultStoragePartition()->GetNetworkContext());
   update_manifest_fetcher_->FetchUpdateManifest(base::BindOnce(
       &IwaInstaller::OnUpdateManifestParsed, weak_factory_.GetWeakPtr(),
       std::move(next_step_callback)));
@@ -367,6 +370,7 @@
   bundle_downloader_ = IsolatedWebAppDownloader::CreateAndStartDownloading(
       std::move(web_bundle_url), bundle_.path(),
       kWebBundleDownloadTrafficAnnotation, url_loader_factory_,
+      provider_->profile()->GetDefaultStoragePartition()->GetNetworkContext(),
       base::BindOnce(&IwaInstaller::OnWebBundleDownloaded,
                      // If `this` is deleted, `bundle_downloader_` is deleted
                      // as well, and thus the callback will never run.
diff --git a/chrome/browser/web_applications/isolated_web_apps/update/isolated_web_app_update_check_and_prepare_task.cc b/chrome/browser/web_applications/isolated_web_apps/update/isolated_web_app_update_check_and_prepare_task.cc
index babd6e6e..1eb0907 100644
--- a/chrome/browser/web_applications/isolated_web_apps/update/isolated_web_app_update_check_and_prepare_task.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/update/isolated_web_app_update_check_and_prepare_task.cc
@@ -43,6 +43,7 @@
 #include "components/webapps/isolated_web_apps/download/bundle_downloader.h"
 #include "components/webapps/isolated_web_apps/types/source.h"
 #include "components/webapps/isolated_web_apps/types/update_channel.h"
+#include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "net/base/net_errors.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -249,7 +250,9 @@
 
   update_manifest_fetcher_ = std::make_unique<UpdateManifestFetcher>(
       task_params_.update_manifest_url(), kUpdateManifestFetchTrafficAnnotation,
-      url_loader_factory_, /*report_histogram_manifest_result=*/true);
+      url_loader_factory_,
+      profile_->GetDefaultStoragePartition()->GetNetworkContext(),
+      /*report_histogram_manifest_result=*/true);
   update_manifest_fetcher_->FetchUpdateManifest(base::BindOnce(
       &IsolatedWebAppUpdateCheckAndPrepareTask::OnUpdateManifestFetched,
       weak_factory_.GetWeakPtr()));
@@ -376,7 +379,9 @@
     return;
   }
 
-  bundle_downloader_ = IsolatedWebAppDownloader::Create(url_loader_factory_);
+  bundle_downloader_ = IsolatedWebAppDownloader::Create(
+      url_loader_factory_,
+      profile_->GetDefaultStoragePartition()->GetNetworkContext());
   if (!rotated_key) {
     CreateTempFile(std::move(*version_entry));
     return;
diff --git a/chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher.cc b/chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher.cc
index 1313c21..836940d 100644
--- a/chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher.cc
@@ -4,18 +4,26 @@
 
 #include "chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher.h"
 
+#include <algorithm>
 #include <optional>
 #include <string>
 
 #include "base/functional/callback.h"
 #include "base/json/json_reader.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
 #include "base/types/expected.h"
 #include "chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest.h"
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/ip_address_space_util.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher_unittest.cc b/chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher_unittest.cc
index e73152e3..bba4a82 100644
--- a/chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher_unittest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher_unittest.cc
@@ -8,17 +8,21 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "base/test/gmock_expected_support.h"
+#include "base/test/run_until.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "base/types/expected.h"
 #include "chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest.h"
 #include "components/webapps/isolated_web_apps/types/update_channel.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_network_context.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "services/network/test/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -48,6 +52,30 @@
 
 constexpr std::string_view k404Url = "https://example.com/404.json";
 
+class FakeNetworkContext : public network::TestNetworkContext {
+ public:
+  void ResolveHost(
+      network::mojom::HostResolverHostPtr host,
+      const net::NetworkAnonymizationKey& network_anonymization_key,
+      network::mojom::ResolveHostParametersPtr optional_parameters,
+      mojo::PendingRemote<network::mojom::ResolveHostClient> response_client)
+      override {
+    mojo::Remote<network::mojom::ResolveHostClient> client(
+        std::move(response_client));
+    client->OnComplete(net::OK, net::ResolveErrorInfo(net::OK),
+                       resolved_addresses_,
+                       /*alternative_endpoints=*/{});
+  }
+
+  void set_resolved_addresses(net::AddressList addresses) {
+    resolved_addresses_ = std::move(addresses);
+  }
+
+ private:
+  net::AddressList resolved_addresses_{
+      net::IPEndPoint(net::IPAddress(8, 8, 8, 8), 80)};
+};
+
 class UpdateManifestFetcherTest : public ::testing::Test {
  public:
   UpdateManifestFetcherTest()
@@ -92,12 +120,13 @@
   base::test::TaskEnvironment task_environment_;
   network::TestURLLoaderFactory test_factory_;
   scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
+  FakeNetworkContext fake_network_context_;
 };
 
 TEST_F(UpdateManifestFetcherTest, FetchesValidManifest) {
-  auto fetcher = UpdateManifestFetcher(GURL(kValidManifestUrl),
-                                       PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
-                                       shared_url_loader_factory_);
+  auto fetcher = UpdateManifestFetcher(
+      GURL(kValidManifestUrl), PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+      shared_url_loader_factory_, &fake_network_context_);
 
   base::test::TestFuture<
       base::expected<UpdateManifest, UpdateManifestFetcher::Error>>
@@ -120,9 +149,9 @@
 }
 
 TEST_F(UpdateManifestFetcherTest, SucceedsWhenManifestHasNoVersions) {
-  auto fetcher = UpdateManifestFetcher(GURL(kManifestWithoutVersionsUrl),
-                                       PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
-                                       shared_url_loader_factory_);
+  auto fetcher = UpdateManifestFetcher(
+      GURL(kManifestWithoutVersionsUrl), PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+      shared_url_loader_factory_, &fake_network_context_);
 
   base::test::TestFuture<
       base::expected<UpdateManifest, UpdateManifestFetcher::Error>>
@@ -134,9 +163,9 @@
 }
 
 TEST_F(UpdateManifestFetcherTest, FailsWhenManifestIsInvalid) {
-  auto fetcher = UpdateManifestFetcher(GURL(kInvalidManifestUrl),
-                                       PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
-                                       shared_url_loader_factory_);
+  auto fetcher = UpdateManifestFetcher(
+      GURL(kInvalidManifestUrl), PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+      shared_url_loader_factory_, &fake_network_context_);
 
   base::test::TestFuture<
       base::expected<UpdateManifest, UpdateManifestFetcher::Error>>
@@ -149,9 +178,9 @@
 }
 
 TEST_F(UpdateManifestFetcherTest, FailsWhenJsonIsInvalid) {
-  auto fetcher = UpdateManifestFetcher(GURL(kInvalidJsonUrl),
-                                       PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
-                                       shared_url_loader_factory_);
+  auto fetcher = UpdateManifestFetcher(
+      GURL(kInvalidJsonUrl), PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+      shared_url_loader_factory_, &fake_network_context_);
 
   base::test::TestFuture<
       base::expected<UpdateManifest, UpdateManifestFetcher::Error>>
@@ -166,7 +195,7 @@
 TEST_F(UpdateManifestFetcherTest, FailedDownload) {
   auto fetcher =
       UpdateManifestFetcher(GURL(k404Url), PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
-                            shared_url_loader_factory_);
+                            shared_url_loader_factory_, &fake_network_context_);
 
   base::test::TestFuture<
       base::expected<UpdateManifest, UpdateManifestFetcher::Error>>
@@ -178,5 +207,120 @@
               ErrorIs(UpdateManifestFetcher::Error::kDownloadFailed));
 }
 
+TEST_F(UpdateManifestFetcherTest, SetsCorrectClientSecurityState) {
+  GURL unknown_url("https://other-example.com/manifest.json");
+  auto fetcher =
+      UpdateManifestFetcher(unknown_url, PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                            shared_url_loader_factory_, &fake_network_context_);
+
+  base::test::TestFuture<
+      base::expected<UpdateManifest, UpdateManifestFetcher::Error>>
+      future;
+  fetcher.FetchUpdateManifest(future.GetCallback());
+  EXPECT_TRUE(
+      base::test::RunUntil([&]() { return test_factory_.NumPending() > 0; }));
+
+  ASSERT_EQ(test_factory_.NumPending(), 1);
+  const network::ResourceRequest& request =
+      test_factory_.GetPendingRequest(0)->request;
+
+  ASSERT_TRUE(request.trusted_params);
+  ASSERT_TRUE(request.trusted_params->client_security_state);
+  EXPECT_EQ(request.trusted_params->client_security_state->ip_address_space,
+            network::mojom::IPAddressSpace::kPublic);
+  EXPECT_TRUE(
+      request.trusted_params->client_security_state->is_web_secure_context);
+  EXPECT_EQ(request.trusted_params->client_security_state
+                ->local_network_access_request_policy,
+            network::mojom::LocalNetworkAccessRequestPolicy::kBlock);
+}
+
+TEST_F(UpdateManifestFetcherTest, SetsCorrectClientSecurityStateForIpLiteral) {
+  GURL ip_url("http://127.0.0.1/manifest.json");
+  // No response added for this URL.
+
+  auto fetcher =
+      UpdateManifestFetcher(ip_url, PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                            shared_url_loader_factory_, &fake_network_context_);
+
+  base::test::TestFuture<
+      base::expected<UpdateManifest, UpdateManifestFetcher::Error>>
+      future;
+  fetcher.FetchUpdateManifest(future.GetCallback());
+  EXPECT_TRUE(
+      base::test::RunUntil([&]() { return test_factory_.NumPending() > 0; }));
+
+  ASSERT_EQ(test_factory_.NumPending(), 1);
+  const network::ResourceRequest& request =
+      test_factory_.GetPendingRequest(0)->request;
+
+  ASSERT_TRUE(request.trusted_params);
+  ASSERT_TRUE(request.trusted_params->client_security_state);
+  EXPECT_EQ(request.trusted_params->client_security_state->ip_address_space,
+            network::mojom::IPAddressSpace::kLoopback);
+}
+
+TEST_F(UpdateManifestFetcherTest,
+       SetsCorrectClientSecurityStateForMultipleAddresses) {
+  GURL mixed_url("https://mixed.com/manifest.json");
+  // One public, one private. Public should win.
+  fake_network_context_.set_resolved_addresses(net::AddressList({
+      net::IPEndPoint(net::IPAddress(192, 168, 0, 1), 80),
+      net::IPEndPoint(net::IPAddress(8, 8, 8, 8), 80),
+  }));
+
+  auto fetcher =
+      UpdateManifestFetcher(mixed_url, PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                            shared_url_loader_factory_, &fake_network_context_);
+
+  base::test::TestFuture<
+      base::expected<UpdateManifest, UpdateManifestFetcher::Error>>
+      future;
+  fetcher.FetchUpdateManifest(future.GetCallback());
+  EXPECT_TRUE(
+      base::test::RunUntil([&]() { return test_factory_.NumPending() > 0; }));
+
+  ASSERT_EQ(test_factory_.NumPending(), 1);
+  const network::ResourceRequest& request =
+      test_factory_.GetPendingRequest(0)->request;
+
+  ASSERT_TRUE(request.trusted_params);
+  ASSERT_TRUE(request.trusted_params->client_security_state);
+  // kPublic should be chosen over kLocal (private).
+  EXPECT_EQ(request.trusted_params->client_security_state->ip_address_space,
+            network::mojom::IPAddressSpace::kPublic);
+}
+
+TEST_F(UpdateManifestFetcherTest,
+       SetsCorrectClientSecurityStateForLocalAndLoopback) {
+  GURL mixed_url("https://mixed.com/manifest.json");
+  // One local, one loopback. Local should win (it's more public).
+  fake_network_context_.set_resolved_addresses(net::AddressList({
+      net::IPEndPoint(net::IPAddress(127, 0, 0, 1), 80),
+      net::IPEndPoint(net::IPAddress(192, 168, 0, 1), 80),
+  }));
+
+  auto fetcher =
+      UpdateManifestFetcher(mixed_url, PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                            shared_url_loader_factory_, &fake_network_context_);
+
+  base::test::TestFuture<
+      base::expected<UpdateManifest, UpdateManifestFetcher::Error>>
+      future;
+  fetcher.FetchUpdateManifest(future.GetCallback());
+  EXPECT_TRUE(
+      base::test::RunUntil([&]() { return test_factory_.NumPending() > 0; }));
+
+  ASSERT_EQ(test_factory_.NumPending(), 1);
+  const network::ResourceRequest& request =
+      test_factory_.GetPendingRequest(0)->request;
+
+  ASSERT_TRUE(request.trusted_params);
+  ASSERT_TRUE(request.trusted_params->client_security_state);
+  // kLocal should be chosen over kLoopback.
+  EXPECT_EQ(request.trusted_params->client_security_state->ip_address_space,
+            network::mojom::IPAddressSpace::kLocal);
+}
+
 }  // namespace
 }  // namespace web_app
diff --git a/components/webapps/isolated_web_apps/download/bundle_downloader_unittest.cc b/components/webapps/isolated_web_apps/download/bundle_downloader_unittest.cc
index 71e2f18..0fa983b 100644
--- a/components/webapps/isolated_web_apps/download/bundle_downloader_unittest.cc
+++ b/components/webapps/isolated_web_apps/download/bundle_downloader_unittest.cc
@@ -11,13 +11,17 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/test/run_until.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_network_context.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "services/network/test/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -30,6 +34,30 @@
 using ::testing::IsFalse;
 using ::testing::IsTrue;
 
+class FakeNetworkContext : public network::TestNetworkContext {
+ public:
+  void ResolveHost(
+      network::mojom::HostResolverHostPtr host,
+      const net::NetworkAnonymizationKey& network_anonymization_key,
+      network::mojom::ResolveHostParametersPtr optional_parameters,
+      mojo::PendingRemote<network::mojom::ResolveHostClient> response_client)
+      override {
+    mojo::Remote<network::mojom::ResolveHostClient> client(
+        std::move(response_client));
+    client->OnComplete(net::OK, net::ResolveErrorInfo(net::OK),
+                       resolved_addresses_,
+                       /*alternative_endpoints=*/{});
+  }
+
+  void set_resolved_addresses(net::AddressList addresses) {
+    resolved_addresses_ = std::move(addresses);
+  }
+
+ private:
+  net::AddressList resolved_addresses_{
+      net::IPEndPoint(net::IPAddress(8, 8, 8, 8), 80)};
+};
+
 class IsolatedWebAppDownloaderTest : public ::testing::Test {
  public:
   IsolatedWebAppDownloaderTest()
@@ -49,6 +77,7 @@
   base::test::TaskEnvironment task_environment_;
   network::TestURLLoaderFactory test_factory_;
   scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
... (truncated)
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.