Enforce policy host restrictions in regard to the same-origin policy.

Bug: 774731
Cq-Include-Trybots: luci.chromium.try:linux_mojo
Change-Id: I811af3256766f6cd7b4466b6b894067e74f652fd
Reviewed-on: https://chromium-review.googlesource.com/c/1050806
Commit-Queue: Nick Peterson <nrpeter@chromium.org>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Reviewed-by: Mike West <mkwst@chromium.org>
Reviewed-by: Matt Menke <mmenke@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: Devlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#599859}
diff --git a/chrome/browser/extensions/background_xhr_browsertest.cc b/chrome/browser/extensions/background_xhr_browsertest.cc
index 4d4ab6d..e24cede 100644
--- a/chrome/browser/extensions/background_xhr_browsertest.cc
+++ b/chrome/browser/extensions/background_xhr_browsertest.cc
@@ -5,11 +5,13 @@
 #include <memory>
 
 #include "base/bind.h"
+#include "base/json/json_reader.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/post_task.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/extensions/extension_with_management_policy_apitest.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_io_data.h"
 #include "chrome/browser/ui/browser.h"
@@ -18,6 +20,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
+#include "content/public/test/browser_test_utils.h"
 #include "extensions/browser/browsertest_util.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_urls.h"
@@ -110,7 +113,7 @@
       "test_http_auth.html", embedded_test_server()->GetURL("/auth-basic")));
 }
 
-class BackgroundXhrWebstoreTest : public ExtensionApiTest {
+class BackgroundXhrWebstoreTest : public ExtensionApiTestWithManagementPolicy {
  public:
   BackgroundXhrWebstoreTest() = default;
   ~BackgroundXhrWebstoreTest() override = default;
@@ -131,59 +134,264 @@
     ASSERT_TRUE(embedded_test_server()->Start());
   }
 
+  bool CanFetch(const Extension* extension, const GURL& url) {
+    content::DOMMessageQueue message_queue;
+    browsertest_util::ExecuteScriptInBackgroundPageNoWait(
+        profile(), extension->id(),
+        base::StringPrintf("canFetch('%s');", url.spec().c_str()));
+    std::string json;
+    EXPECT_TRUE(message_queue.WaitForMessage(&json));
+    base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS);
+    std::unique_ptr<base::Value> value = reader.ReadToValue(json);
+    std::string result;
+    EXPECT_TRUE(value->GetAsString(&result));
+    EXPECT_TRUE(result == "true" || result == "false") << result;
+    return result == "true";
+  }
+
+  const Extension* LoadXhrExtension(const std::string& host) {
+    ExtensionTestMessageListener listener("ready", false);
+    TestExtensionDir test_dir;
+    test_dir.WriteManifest(R"(
+    {
+      "name": "XHR Test",
+      "manifest_version": 2,
+      "version": "0.1",
+      "background": {"scripts": ["background.js"]},
+      "permissions": [")" + host + R"("]
+    })");
+    constexpr char kBackgroundScriptFile[] = R"(
+    function canFetch(url) {
+      console.warn('Fetching: ' + url);
+      fetch(url).then((response) => {
+        domAutomationController.send('true');
+      }).catch((e) => {
+        let message;
+        if (e.message == 'Failed to fetch')
+          message = 'false'
+        else
+          message = 'Unexpected Error: ' + e.message;
+        domAutomationController.send(message);
+      });
+    }
+    chrome.test.sendMessage('ready');)";
+
+    test_dir.WriteFile(FILE_PATH_LITERAL("background.js"),
+                       kBackgroundScriptFile);
+    const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+    EXPECT_TRUE(listener.WaitUntilSatisfied());
+    return extension;
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(BackgroundXhrWebstoreTest);
 };
 
 // Extensions should not be able to XHR to the webstore.
 IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, XHRToWebstore) {
-  TestExtensionDir test_dir;
-  test_dir.WriteManifest(R"(
-    {
-      "name": "XHR Test",
-      "manifest_version": 2,
-      "version": "0.1",
-      "background": {"scripts": ["background.js"]},
-      "permissions": ["<all_urls>"]
-    })");
-  constexpr char kBackgroundScriptFile[] =
-      R"(function canFetch(url) {
-           console.warn('Fetching: ' + url);
-           fetch(url).then((response) => {
-             domAutomationController.send('true');
-           }).catch((e) => {
-             domAutomationController.send('false');
-           });
-         }
-         chrome.test.sendMessage('ready');)";
-
-  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundScriptFile);
-
-  ExtensionTestMessageListener listener("ready", false);
-  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
-  ASSERT_TRUE(extension);
-  EXPECT_TRUE(listener.WaitUntilSatisfied());
-
-  content::BrowserContext* browser_context = profile();
-  auto can_fetch = [extension, browser_context](const GURL& url) {
-    std::string result = browsertest_util::ExecuteScriptInBackgroundPage(
-        browser_context, extension->id(),
-        base::StringPrintf("canFetch('%s');", url.spec().c_str()));
-    EXPECT_TRUE(result == "true" || result == "false")
-        << "Unexpected result: " << result;
-    return result == "true";
-  };
+  const Extension* extension = LoadXhrExtension("<all_urls>");
 
   GURL webstore_launch_url = extension_urls::GetWebstoreLaunchURL();
   GURL webstore_url_to_fetch = embedded_test_server()->GetURL(
       webstore_launch_url.host(), "/simple.html");
 
-  EXPECT_FALSE(can_fetch(webstore_url_to_fetch));
+  EXPECT_FALSE(CanFetch(extension, webstore_url_to_fetch));
 
   // Sanity check: the extension should be able to fetch google.com.
   GURL google_url =
       embedded_test_server()->GetURL("google.com", "/simple.html");
-  EXPECT_TRUE(can_fetch(google_url));
+  EXPECT_TRUE(CanFetch(extension, google_url));
+}
+
+// Extensions should not be able to XHR to the webstore regardless of policy.
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, XHRToWebstorePolicy) {
+  {
+    ExtensionManagementPolicyUpdater pref(&policy_provider_);
+    pref.AddPolicyAllowedHost(
+        "*", "*://" + extension_urls::GetWebstoreLaunchURL().host());
+  }
+
+  const Extension* extension = LoadXhrExtension("<all_urls>");
+
+  GURL webstore_launch_url = extension_urls::GetWebstoreLaunchURL();
+  GURL webstore_url_to_fetch = embedded_test_server()->GetURL(
+      webstore_launch_url.host(), "/simple.html");
+
+  EXPECT_FALSE(CanFetch(extension, webstore_url_to_fetch));
+
+  // Sanity check: the extension should be able to fetch google.com.
+  GURL google_url =
+      embedded_test_server()->GetURL("google.com", "/simple.html");
+  EXPECT_TRUE(CanFetch(extension, google_url));
+}
+
+// Extensions should not be able to bypass same-origin despite declaring
+// <all_urls> for hosts restricted by enterprise policy.
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, PolicyBlockedXHR) {
+  {
+    ExtensionManagementPolicyUpdater pref(&policy_provider_);
+    pref.AddPolicyBlockedHost("*", "*://*.example.com");
+    pref.AddPolicyAllowedHost("*", "*://public.example.com");
+  }
+
+  const Extension* extension = LoadXhrExtension("<all_urls>");
+
+  // Should block due to "runtime_blocked_hosts" section of policy.
+  GURL protected_url_to_fetch =
+      embedded_test_server()->GetURL("example.com", "/simple.html");
+  EXPECT_FALSE(CanFetch(extension, protected_url_to_fetch));
+
+  // Should allow due to "runtime_allowed_hosts" section of policy.
+  GURL exempted_url_to_fetch =
+      embedded_test_server()->GetURL("public.example.com", "/simple.html");
+  EXPECT_TRUE(CanFetch(extension, exempted_url_to_fetch));
+}
+
+// Verify that policy blocklists apply to XHRs done from injected scripts.
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, PolicyContentScriptXHR) {
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(R"(
+    {
+      "name": "XHR Content Script Test",
+      "manifest_version": 2,
+      "version": "0.1",
+      "permissions": ["<all_urls>", "tabs"],
+      "background": {"scripts": ["background.js"]}
+    })");
+
+  constexpr char kBackgroundScript[] =
+      R"(function canFetch(url) {
+           chrome.tabs.executeScript({code: `
+             fetch("${url}")
+             .then(response => response.text())
+             .then(text => domAutomationController.send('true'))
+             .catch(err => domAutomationController.send('false'));
+           `});
+         }
+      )";
+  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundScript);
+
+  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  // Navigate to a foo.com page.
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  GURL page_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
+  ui_test_utils::NavigateToURL(browser(), page_url);
+  EXPECT_EQ(page_url, web_contents->GetMainFrame()->GetLastCommittedURL());
+
+  GURL example_url =
+      embedded_test_server()->GetURL("example.com", "/simple.html");
+  GURL public_example_url =
+      embedded_test_server()->GetURL("public.example.com", "/simple.html");
+
+  // Sanity Check: Should be able to fetch cross origin.
+  EXPECT_TRUE(CanFetch(extension, example_url));
+  EXPECT_TRUE(CanFetch(extension, public_example_url));
+
+  {
+    ExtensionManagementPolicyUpdater pref(&policy_provider_);
+    pref.AddPolicyBlockedHost("*", "*://*.example.com");
+    pref.AddPolicyAllowedHost("*", "*://public.example.com");
+  }
+
+  // Policies apply to XHR from a content script.
+  EXPECT_FALSE(CanFetch(extension, example_url));
+  EXPECT_TRUE(CanFetch(extension, public_example_url));
+}
+
+// Make sure the blocklist and allowlist update for both Default and Individual
+// scope policies. Testing with all host permissions granted (<all_urls>).
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, PolicyUpdateXHR) {
+  const Extension* extension = LoadXhrExtension("<all_urls>");
+
+  GURL example_url =
+      embedded_test_server()->GetURL("example.com", "/simple.html");
+  GURL public_example_url =
+      embedded_test_server()->GetURL("public.example.com", "/simple.html");
+
+  // Sanity check: Without restrictions all fetches should work.
+  EXPECT_TRUE(CanFetch(extension, public_example_url));
+  EXPECT_TRUE(CanFetch(extension, example_url));
+
+  {
+    ExtensionManagementPolicyUpdater pref(&policy_provider_);
+    pref.AddPolicyBlockedHost("*", "*://*.example.com");
+    pref.AddPolicyAllowedHost("*", "*://public.example.com");
+  }
+
+  // Default policies propagate.
+  EXPECT_TRUE(CanFetch(extension, public_example_url));
+  EXPECT_FALSE(CanFetch(extension, example_url));
+  {
+    ExtensionManagementPolicyUpdater pref(&policy_provider_);
+    pref.AddPolicyBlockedHost(extension->id(), "*://*.example2.com");
+    pref.AddPolicyAllowedHost(extension->id(), "*://public.example2.com");
+  }
+
+  // Default policies overridden when individual scope policies applied.
+  EXPECT_TRUE(CanFetch(extension, public_example_url));
+  EXPECT_TRUE(CanFetch(extension, example_url));
+
+  GURL example2_url =
+      embedded_test_server()->GetURL("example2.com", "/simple.html");
+  GURL public_example2_url =
+      embedded_test_server()->GetURL("public.example2.com", "/simple.html");
+
+  // Individual scope policies propagate.
+  EXPECT_TRUE(CanFetch(extension, public_example2_url));
+  EXPECT_FALSE(CanFetch(extension, example2_url));
+}
+
+// Make sure the allowlist entries added due to host permissions are removed
+// when a more generic blocklist policy is updated and contains them.
+// This tests the default policy scope update.
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, PolicyUpdateDefaultXHR) {
+  const Extension* extension = LoadXhrExtension("*://public.example.com/*");
+
+  GURL example_url =
+      embedded_test_server()->GetURL("example.com", "/simple.html");
+  GURL public_example_url =
+      embedded_test_server()->GetURL("public.example.com", "/simple.html");
+
+  // Sanity check: Without restrictions only public.example.com should work.
+  EXPECT_TRUE(CanFetch(extension, public_example_url));
+  EXPECT_FALSE(CanFetch(extension, example_url));
+
+  {
+    ExtensionManagementPolicyUpdater pref(&policy_provider_);
+    pref.AddPolicyBlockedHost("*", "*://*.example.com");
+  }
+
+  // The blocklist of example.com overrides allowlist of public.example.com.
+  EXPECT_FALSE(CanFetch(extension, example_url));
+  EXPECT_FALSE(CanFetch(extension, public_example_url));
+}
+
+// Make sure the allowlist entries added due to host permissions are removed
+// when a more generic blocklist policy is updated and contains them.
+// This tests an individual policy scope update.
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, PolicyUpdateIndividualXHR) {
+  const Extension* extension = LoadXhrExtension("*://public.example.com/*");
+
+  GURL example_url =
+      embedded_test_server()->GetURL("example.com", "/simple.html");
+  GURL public_example_url =
+      embedded_test_server()->GetURL("public.example.com", "/simple.html");
+
+  // Sanity check: Without restrictions only public.example.com should work.
+  EXPECT_TRUE(CanFetch(extension, public_example_url));
+  EXPECT_FALSE(CanFetch(extension, example_url));
+
+  {
+    ExtensionManagementPolicyUpdater pref(&policy_provider_);
+    pref.AddPolicyBlockedHost(extension->id(), "*://*.example.com");
+  }
+
+  // The blocklist of example.com overrides allowlist of public.example.com.
+  EXPECT_FALSE(CanFetch(extension, example_url));
+  EXPECT_FALSE(CanFetch(extension, public_example_url));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/content_script_apitest.cc b/chrome/browser/extensions/content_script_apitest.cc
index 697c21f..c05db45 100644
--- a/chrome/browser/extensions/content_script_apitest.cc
+++ b/chrome/browser/extensions/content_script_apitest.cc
@@ -404,7 +404,7 @@
   ASSERT_TRUE(RunExtensionTest("content_scripts/policy")) << message_;
 }
 
-// Verifies wildcard can be used for effecitve TLD.
+// Verifies wildcard can NOT be used for effective TLD.
 IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy,
                        ContentScriptPolicyWildcard) {
   // Set enterprise policy to block injection to policy specified hosts.
@@ -413,7 +413,7 @@
     pref.AddPolicyBlockedHost("*", "*://example.*");
   }
   ASSERT_TRUE(StartEmbeddedTestServer());
-  ASSERT_TRUE(RunExtensionTest("content_scripts/policy")) << message_;
+  ASSERT_FALSE(RunExtensionTest("content_scripts/policy")) << message_;
 }
 
 IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy,
diff --git a/chrome/browser/extensions/extension_management_internal.cc b/chrome/browser/extensions/extension_management_internal.cc
index 7b831fc6..18a8069a4 100644
--- a/chrome/browser/extensions/extension_management_internal.cc
+++ b/chrome/browser/extensions/extension_management_internal.cc
@@ -135,8 +135,10 @@
         URLPattern pattern(extension_scheme_mask);
         if (unparsed_str != URLPattern::kAllUrlsPattern)
           unparsed_str.append("/*");
+        // TODO(nrpeter): Remove effective TLD wildcard capability from
+        // URLPattern.
         URLPattern::ParseResult parse_result = pattern.Parse(
-            unparsed_str, URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD);
+            unparsed_str, URLPattern::DENY_WILDCARD_FOR_EFFECTIVE_TLD);
         if (parse_result != URLPattern::ParseResult::kSuccess) {
           LOG(WARNING) << kMalformedPreferenceWarning;
           LOG(WARNING) << "Invalid URL pattern '" + unparsed_str +
diff --git a/chrome/browser/extensions/extension_management_test_util.cc b/chrome/browser/extensions/extension_management_test_util.cc
index 21b667b..3eaab625 100644
--- a/chrome/browser/extensions/extension_management_test_util.cc
+++ b/chrome/browser/extensions/extension_management_test_util.cc
@@ -198,6 +198,34 @@
   RemoveStringFromList(make_path(prefix, schema::kPolicyBlockedHosts), host);
 }
 
+// Helper functions for 'runtime_allowed_hosts' manipulation ------------------
+
+void ExtensionManagementPrefUpdaterBase::UnsetPolicyAllowedHosts(
+    const std::string& prefix) {
+  DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix));
+  pref_->Remove(make_path(prefix, schema::kPolicyAllowedHosts), nullptr);
+}
+
+void ExtensionManagementPrefUpdaterBase::ClearPolicyAllowedHosts(
+    const std::string& prefix) {
+  DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix));
+  ClearList(make_path(prefix, schema::kPolicyAllowedHosts));
+}
+
+void ExtensionManagementPrefUpdaterBase::AddPolicyAllowedHost(
+    const std::string& prefix,
+    const std::string& host) {
+  DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix));
+  AddStringToList(make_path(prefix, schema::kPolicyAllowedHosts), host);
+}
+
+void ExtensionManagementPrefUpdaterBase::RemovePolicyAllowedHost(
+    const std::string& prefix,
+    const std::string& host) {
+  DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix));
+  RemoveStringFromList(make_path(prefix, schema::kPolicyAllowedHosts), host);
+}
+
 // Helper functions for 'allowed_permissions' manipulation ---------------------
 
 void ExtensionManagementPrefUpdaterBase::UnsetAllowedPermissions(
diff --git a/chrome/browser/extensions/extension_management_test_util.h b/chrome/browser/extensions/extension_management_test_util.h
index dd05cc1..cda5537d 100644
--- a/chrome/browser/extensions/extension_management_test_util.h
+++ b/chrome/browser/extensions/extension_management_test_util.h
@@ -74,6 +74,14 @@
   void RemovePolicyBlockedHost(const std::string& prefix,
                                const std::string& host);
 
+  // Helper functions for 'runtime_allowed_hosts' manipulation. |prefix| can be
+  // kWildCard or a valid extension ID.
+  void UnsetPolicyAllowedHosts(const std::string& prefix);
+  void ClearPolicyAllowedHosts(const std::string& prefix);
+  void AddPolicyAllowedHost(const std::string& prefix, const std::string& host);
+  void RemovePolicyAllowedHost(const std::string& prefix,
+                               const std::string& host);
+
   // Helper functions for 'allowed_permissions' manipulation. |id| must be a
   // valid extension ID.
   void UnsetAllowedPermissions(const std::string& id);
diff --git a/chrome/browser/extensions/policy_handlers.cc b/chrome/browser/extensions/policy_handlers.cc
index fc63cc0..4be1677 100644
--- a/chrome/browser/extensions/policy_handlers.cc
+++ b/chrome/browser/extensions/policy_handlers.cc
@@ -305,7 +305,7 @@
           unparsed_urls->GetString(i, &unparsed_url);
           URLPattern pattern(extension_scheme_mask);
           URLPattern::ParseResult parse_result = pattern.Parse(
-              unparsed_url, URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD);
+              unparsed_url, URLPattern::DENY_WILDCARD_FOR_EFFECTIVE_TLD);
           // These keys don't support paths due to how we track the initiator
           // of a webRequest and cookie security policy. We expect a valid
           // pattern to return a PARSE_ERROR_EMPTY_PATH.
@@ -313,7 +313,7 @@
             // Add a wildcard path to the URL as it should match any path.
             parse_result =
                 pattern.Parse(unparsed_url + "/*",
-                              URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD);
+                              URLPattern::DENY_WILDCARD_FOR_EFFECTIVE_TLD);
           } else if (parse_result == URLPattern::ParseResult::kSuccess) {
             // The user supplied a path, notify them that this is not supported.
             if (!pattern.match_all_urls()) {
diff --git a/chrome/browser/extensions/policy_handlers_unittest.cc b/chrome/browser/extensions/policy_handlers_unittest.cc
index 01606b5..edbe164 100644
--- a/chrome/browser/extensions/policy_handlers_unittest.cc
+++ b/chrome/browser/extensions/policy_handlers_unittest.cc
@@ -96,15 +96,14 @@
 }
 
 TEST(ExtensionSettingsPolicyHandlerTest, CheckPolicySettingsURL) {
-  std::vector<std::string> good_urls = {
-      "*://*.example.com", "*://example.com", "http://cat.example.com",
-      "https://example.*", "*://*.example.*", "<all_urls>"};
+  std::vector<std::string> good_urls = {"*://*.example.com", "*://example.com",
+                                        "http://cat.example.com", "<all_urls>"};
 
   // Invalid URLPattern or with a non-standard path
   std::vector<std::string> bad_urls = {
       "://*.example.com",       "*://example.com/cat*",  "*://example.com/",
       "*://*.example.com/*cat", "*://example.com/cat/*", "bad",
-      "*://example.com/*"};
+      "*://example.com/*",      "https://example.*",     "*://*.example.*"};
 
   // Crafts and parses a ExtensionSettings policy to test URL parsing.
   auto url_parses_successfully = [](const char* policy_template,
diff --git a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
index 8306902..e31504f 100644
--- a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
+++ b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
@@ -74,12 +74,16 @@
   // to avoid granting them in "unblessed" (non-extension) processes.  If a
   // component extension somehow starts as inactive and becomes active later,
   // we'll re-init the origin permissions, so there's no danger in being
-  // conservative.
+  // conservative. Components shouldn't be subject to enterprise policy controls
+  // or blocking access to the webstore so they get the highest priority
+  // allowlist entry.
   if (extensions::Manifest::IsComponentLocation(extension.location()) &&
       is_extension_active) {
     blink::WebSecurityPolicy::AddOriginAccessAllowListEntry(
         extension.url(), blink::WebString::FromUTF8(content::kChromeUIScheme),
-        blink::WebString::FromUTF8(chrome::kChromeUIThemeHost), false);
+        blink::WebString::FromUTF8(chrome::kChromeUIThemeHost),
+        false /*allow_destination_subdomains*/,
+        network::mojom::CORSOriginAccessMatchPriority::kMaxPriority);
   }
 
   // TODO(jstritar): We should try to remove this special case. Also, these
@@ -89,7 +93,9 @@
                                  extensions::APIPermission::kManagement)) {
     blink::WebSecurityPolicy::AddOriginAccessAllowListEntry(
         extension.url(), blink::WebString::FromUTF8(content::kChromeUIScheme),
-        blink::WebString::FromUTF8(chrome::kChromeUIExtensionIconHost), false);
+        blink::WebString::FromUTF8(chrome::kChromeUIExtensionIconHost),
+        false /*allow_destination_subdomains*/,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
   }
 }
 
diff --git a/content/browser/loader/cors_origin_access_list_browsertest.cc b/content/browser/loader/cors_origin_access_list_browsertest.cc
index b832ca4..8f32876 100644
--- a/content/browser/loader/cors_origin_access_list_browsertest.cc
+++ b/content/browser/loader/cors_origin_access_list_browsertest.cc
@@ -21,6 +21,7 @@
 #include "net/base/host_port_pair.h"
 #include "net/dns/mock_host_resolver.h"
 #include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/cors.mojom.h"
 #include "services/network/public/mojom/cors_origin_pattern.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
@@ -102,8 +103,9 @@
                     const std::string& host,
                     bool allow_subdomains) {
     std::vector<network::mojom::CorsOriginPatternPtr> list1;
-    list1.push_back(
-        network::mojom::CorsOriginPattern::New(scheme, host, allow_subdomains));
+    list1.push_back(network::mojom::CorsOriginPattern::New(
+        scheme, host, allow_subdomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority));
     bool first_list_done = false;
     BrowserContext::SetCorsOriginAccessListsForOrigin(
         shell()->web_contents()->GetBrowserContext(),
@@ -113,8 +115,9 @@
                        base::Unretained(&first_list_done)));
 
     std::vector<network::mojom::CorsOriginPatternPtr> list2;
-    list2.push_back(
-        network::mojom::CorsOriginPattern::New(scheme, host, allow_subdomains));
+    list2.push_back(network::mojom::CorsOriginPattern::New(
+        scheme, host, allow_subdomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority));
     bool second_list_done = false;
     BrowserContext::SetCorsOriginAccessListsForOrigin(
         shell()->web_contents()->GetBrowserContext(),
diff --git a/content/public/test/network_service_test_helper.cc b/content/public/test/network_service_test_helper.cc
index 2d81fbb5a..6556ecc 100644
--- a/content/public/test/network_service_test_helper.cc
+++ b/content/public/test/network_service_test_helper.cc
@@ -41,9 +41,11 @@
 namespace content {
 namespace {
 
+#ifndef STATIC_ASSERT_ENUM
 #define STATIC_ASSERT_ENUM(a, b)                            \
   static_assert(static_cast<int>(a) == static_cast<int>(b), \
                 "mismatching enums: " #a)
+#endif
 
 STATIC_ASSERT_ENUM(network::mojom::ResolverType::kResolverTypeFail,
                    net::RuleBasedHostResolverProc::Rule::kResolverTypeFail);
diff --git a/content/shell/test_runner/DEPS b/content/shell/test_runner/DEPS
index 8796790..42b1e66 100644
--- a/content/shell/test_runner/DEPS
+++ b/content/shell/test_runner/DEPS
@@ -11,6 +11,7 @@
   "+net/base",
   "+services/device/public/cpp/generic_sensor",
   "+services/device/public/mojom",
+  "+services/network/public/mojom/cors.mojom.h",
   "+services/service_manager/public/cpp",
   "+skia",
   "+third_party/khronos/GLES2/gl2.h",
diff --git a/content/shell/test_runner/test_runner.cc b/content/shell/test_runner/test_runner.cc
index 653d3b1b..dcabe31 100644
--- a/content/shell/test_runner/test_runner.cc
+++ b/content/shell/test_runner/test_runner.cc
@@ -38,6 +38,7 @@
 #include "gin/handle.h"
 #include "gin/object_template_builder.h"
 #include "gin/wrappable.h"
+#include "services/network/public/mojom/cors.mojom.h"
 #include "third_party/blink/public/platform/modules/service_worker/web_service_worker_registration.h"
 #include "third_party/blink/public/platform/web_data.h"
 #include "third_party/blink/public/platform/web_point.h"
@@ -2109,7 +2110,8 @@
   blink::WebSecurityPolicy::AddOriginAccessAllowListEntry(
       url, blink::WebString::FromUTF8(destination_protocol),
       blink::WebString::FromUTF8(destination_host),
-      allow_destination_subdomains);
+      allow_destination_subdomains,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
 }
 
 void TestRunner::SetTextSubpixelPositioning(bool value) {
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index da2c272..25959d38 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -100,6 +100,7 @@
 #include "extensions/renderer/worker_thread_dispatcher.h"
 #include "gin/converter.h"
 #include "mojo/public/js/grit/mojo_bindings_resources.h"
+#include "services/network/public/mojom/cors.mojom.h"
 #include "third_party/blink/public/platform/web_runtime_features.h"
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/platform/web_url_request.h"
@@ -1104,6 +1105,16 @@
     const ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params& params) {
   PermissionsData::SetDefaultPolicyHostRestrictions(
       params.default_policy_blocked_hosts, params.default_policy_allowed_hosts);
+  // Update blink host permission allowlist exceptions for all loaded
+  // extensions.
+  for (const std::string& extension_id :
+       RendererExtensionRegistry::Get()->GetIDs()) {
+    const Extension* extension =
+        RendererExtensionRegistry::Get()->GetByID(extension_id);
+    if (extension->permissions_data()->UsesDefaultPolicyHostRestrictions()) {
+      UpdateOriginPermissions(*extension);
+    }
+  }
   UpdateBindings(std::string());
 }
 
@@ -1114,6 +1125,13 @@
   if (!extension)
     return;
 
+  if (params.uses_default_policy_host_restrictions) {
+    extension->permissions_data()->SetUsesDefaultHostRestrictions();
+  } else {
+    extension->permissions_data()->SetPolicyHostRestrictions(
+        params.policy_blocked_hosts, params.policy_allowed_hosts);
+  }
+
   std::unique_ptr<const PermissionSet> active =
       params.active_permissions.ToPermissionSet();
   std::unique_ptr<const PermissionSet> withheld =
@@ -1188,14 +1206,6 @@
 }
 
 void Dispatcher::InitOriginPermissions(const Extension* extension) {
-  const GURL webstore_launch_url = extension_urls::GetWebstoreLaunchURL();
-  WebSecurityPolicy::AddOriginAccessBlockListEntry(
-      extension->url(), WebString::FromUTF8(webstore_launch_url.scheme()),
-      WebString::FromUTF8(webstore_launch_url.host()), true);
-
-  // TODO(devlin): Should we also block the webstore update URL here? See
-  // https://crbug.com/826946 for a related instance.
-
   UpdateOriginPermissions(*extension);
 }
 
@@ -1213,24 +1223,61 @@
   };
 
   // Remove all old patterns associated with this extension.
-  WebSecurityPolicy::ClearOriginAccessAllowListForOrigin(extension.url());
+  WebSecurityPolicy::ClearOriginAccessListForOrigin(extension.url());
 
   delegate_->AddOriginAccessPermissions(extension,
                                         IsExtensionActive(extension.id()));
 
-  URLPatternSet patterns =
+  URLPatternSet origin_permissions =
       extension.permissions_data()->GetEffectiveHostPermissions();
 
-  for (size_t i = 0; i < arraysize(kSchemes); ++i) {
-    const char* scheme = kSchemes[i];
-    for (const auto& pattern : patterns) {
-      if (pattern.MatchesScheme(scheme)) {
+  // Permissions declared by the extension.
+  for (const URLPattern& pattern : origin_permissions) {
+    for (const char* scheme : kSchemes) {
+      if (pattern.MatchesScheme(scheme))
         WebSecurityPolicy::AddOriginAccessAllowListEntry(
             extension.url(), WebString::FromUTF8(scheme),
-            WebString::FromUTF8(pattern.host()), pattern.match_subdomains());
-      }
+            WebString::FromUTF8(pattern.host()), pattern.match_subdomains(),
+            network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
     }
   }
+
+  // Hosts blocked by enterprise policy.
+  for (const URLPattern& pattern :
+       extension.permissions_data()->policy_blocked_hosts()) {
+    for (const char* scheme : kSchemes) {
+      if (pattern.MatchesScheme(scheme))
+        WebSecurityPolicy::AddOriginAccessBlockListEntry(
+            extension.url(), WebString::FromUTF8(scheme),
+            WebString::FromUTF8(pattern.host()), pattern.match_subdomains(),
+            network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+    }
+  }
+
+  // Hosts exempted from the enterprise policy blocklist.
+  // This set intersection is necessary to prevent an enterprise policy from
+  // granting a host permission the extension didn't ask for.
+  URLPatternSet overlap = URLPatternSet::CreateIntersection(
+      extension.permissions_data()->policy_allowed_hosts(), origin_permissions,
+      URLPatternSet::IntersectionBehavior::kDetailed);
+  for (const URLPattern& pattern : overlap) {
+    for (const char* scheme : kSchemes) {
+      if (pattern.MatchesScheme(scheme))
+        WebSecurityPolicy::AddOriginAccessAllowListEntry(
+            extension.url(), WebString::FromUTF8(scheme),
+            WebString::FromUTF8(pattern.host()), pattern.match_subdomains(),
+            network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+    }
+  };
+
+  const GURL webstore_launch_url = extension_urls::GetWebstoreLaunchURL();
+  WebSecurityPolicy::AddOriginAccessBlockListEntry(
+      extension.url(), WebString::FromUTF8(webstore_launch_url.scheme()),
+      WebString::FromUTF8(webstore_launch_url.host()), true,
+      network::mojom::CORSOriginAccessMatchPriority::kHighPriority);
+
+  // TODO(devlin): Should we also block the webstore update URL here? See
+  // https://crbug.com/826946 for a related instance.
 }
 
 void Dispatcher::EnableCustomElementWhiteList() {
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index 155e1858..bc0164b3 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -18,6 +18,7 @@
 #include "net/url_request/url_request.h"
 #include "services/network/cors/cors_url_loader_factory.h"
 #include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/cors.mojom.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "services/network/test/test_url_loader_client.h"
@@ -228,8 +229,9 @@
                                   const std::string& protocol,
                                   const std::string& domain,
                                   bool allow_subdomains) {
-    origin_access_list_.AddAllowListEntryForOrigin(source_origin, protocol,
-                                                   domain, allow_subdomains);
+    origin_access_list_.AddAllowListEntryForOrigin(
+        source_origin, protocol, domain, allow_subdomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
   }
 
   static net::RedirectInfo CreateRedirectInfo(
diff --git a/services/network/public/cpp/cors/origin_access_entry.cc b/services/network/public/cpp/cors/origin_access_entry.cc
index 6b7400d..26361e4 100644
--- a/services/network/public/cpp/cors/origin_access_entry.cc
+++ b/services/network/public/cpp/cors/origin_access_entry.cc
@@ -30,12 +30,15 @@
 
 }  // namespace
 
-OriginAccessEntry::OriginAccessEntry(const std::string& protocol,
-                                     const std::string& host,
-                                     MatchMode match_mode)
+OriginAccessEntry::OriginAccessEntry(
+    const std::string& protocol,
+    const std::string& host,
+    MatchMode match_mode,
+    const network::mojom::CORSOriginAccessMatchPriority priority)
     : protocol_(protocol),
       host_(host),
       match_mode_(match_mode),
+      priority_(priority),
       host_is_ip_address_(url::HostIsIPAddress(host)),
       host_is_public_suffix_(false) {
   if (host_is_ip_address_)
diff --git a/services/network/public/cpp/cors/origin_access_entry.h b/services/network/public/cpp/cors/origin_access_entry.h
index 46a35c2..5052f1a 100644
--- a/services/network/public/cpp/cors/origin_access_entry.h
+++ b/services/network/public/cpp/cors/origin_access_entry.h
@@ -9,6 +9,7 @@
 
 #include "base/component_export.h"
 #include "base/macros.h"
+#include "services/network/public/mojom/cors.mojom-shared.h"
 
 namespace url {
 class Origin;
@@ -46,9 +47,14 @@
   // will match all domains in the specified protocol.
   // IPv6 addresses must include brackets (e.g.
   // '[2001:db8:85a3::8a2e:370:7334]', not '2001:db8:85a3::8a2e:370:7334').
-  OriginAccessEntry(const std::string& protocol,
-                    const std::string& host,
-                    MatchMode match_mode);
+  // The priority argument is used to break ties when multiple entries
+  // match.
+  OriginAccessEntry(
+      const std::string& protocol,
+      const std::string& host,
+      MatchMode match_mode,
+      const network::mojom::CORSOriginAccessMatchPriority priority =
+          network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
   OriginAccessEntry(OriginAccessEntry&& from);
 
   // 'matchesOrigin' requires a protocol match (e.g. 'http' != 'https').
@@ -57,6 +63,9 @@
   MatchResult MatchesDomain(const url::Origin& domain) const;
 
   bool host_is_ip_address() const { return host_is_ip_address_; }
+  network::mojom::CORSOriginAccessMatchPriority priority() const {
+    return priority_;
+  }
   const std::string& registerable_domain() const {
     return registerable_domain_;
   }
@@ -65,6 +74,7 @@
   const std::string protocol_;
   const std::string host_;
   const MatchMode match_mode_;
+  network::mojom::CORSOriginAccessMatchPriority priority_;
   const bool host_is_ip_address_;
 
   std::string registerable_domain_;
diff --git a/services/network/public/cpp/cors/origin_access_entry_unittest.cc b/services/network/public/cpp/cors/origin_access_entry_unittest.cc
index d814028c..a68e978 100644
--- a/services/network/public/cpp/cors/origin_access_entry_unittest.cc
+++ b/services/network/public/cpp/cors/origin_access_entry_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "services/network/public/cpp/cors/origin_access_entry.h"
+#include "services/network/public/mojom/cors.mojom.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -16,11 +17,15 @@
 
 TEST(OriginAccessEntryTest, PublicSuffixListTest) {
   url::Origin origin = url::Origin::Create(GURL("http://www.google.com"));
-  OriginAccessEntry entry1("http", "google.com",
-                           OriginAccessEntry::kAllowSubdomains);
-  OriginAccessEntry entry2("http", "hamster.com",
-                           OriginAccessEntry::kAllowSubdomains);
-  OriginAccessEntry entry3("http", "com", OriginAccessEntry::kAllowSubdomains);
+  OriginAccessEntry entry1(
+      "http", "google.com", OriginAccessEntry::kAllowSubdomains,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+  OriginAccessEntry entry2(
+      "http", "hamster.com", OriginAccessEntry::kAllowSubdomains,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+  OriginAccessEntry entry3(
+      "http", "com", OriginAccessEntry::kAllowSubdomains,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
   EXPECT_EQ(OriginAccessEntry::kMatchesOrigin, entry1.MatchesOrigin(origin));
   EXPECT_EQ(OriginAccessEntry::kDoesNotMatchOrigin,
             entry2.MatchesOrigin(origin));
@@ -86,8 +91,9 @@
     SCOPED_TRACE(testing::Message()
                  << "Host: " << test.host << ", Origin: " << test.origin);
     url::Origin origin_to_test = url::Origin::Create(GURL(test.origin));
-    OriginAccessEntry entry1(test.protocol, test.host,
-                             OriginAccessEntry::kAllowSubdomains);
+    OriginAccessEntry entry1(
+        test.protocol, test.host, OriginAccessEntry::kAllowSubdomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
     EXPECT_EQ(test.expected_origin, entry1.MatchesOrigin(origin_to_test));
     EXPECT_EQ(test.expected_domain, entry1.MatchesDomain(origin_to_test));
   }
@@ -134,8 +140,9 @@
 
   for (const auto& test : inputs) {
     url::Origin origin_to_test = url::Origin::Create(GURL(test.origin));
-    OriginAccessEntry entry1(test.protocol, test.host,
-                             OriginAccessEntry::kAllowRegisterableDomains);
+    OriginAccessEntry entry1(
+        test.protocol, test.host, OriginAccessEntry::kAllowRegisterableDomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
 
     SCOPED_TRACE(testing::Message()
                  << "Host: " << test.host << ", Origin: " << test.origin
@@ -186,8 +193,9 @@
 
   for (const auto& test : inputs) {
     url::Origin origin_to_test = url::Origin::Create(GURL(test.origin));
-    OriginAccessEntry entry1(test.protocol, test.host,
-                             OriginAccessEntry::kAllowRegisterableDomains);
+    OriginAccessEntry entry1(
+        test.protocol, test.host, OriginAccessEntry::kAllowRegisterableDomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
 
     SCOPED_TRACE(testing::Message()
                  << "Host: " << test.host << ", Origin: " << test.origin
@@ -235,8 +243,9 @@
     SCOPED_TRACE(testing::Message()
                  << "Host: " << test.host << ", Origin: " << test.origin);
     url::Origin origin_to_test = url::Origin::Create(GURL(test.origin));
-    OriginAccessEntry entry1(test.protocol, test.host,
-                             OriginAccessEntry::kDisallowSubdomains);
+    OriginAccessEntry entry1(
+        test.protocol, test.host, OriginAccessEntry::kDisallowSubdomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
     EXPECT_EQ(test.expected, entry1.MatchesOrigin(origin_to_test));
   }
 }
@@ -260,8 +269,9 @@
 
   for (const auto& test : inputs) {
     SCOPED_TRACE(testing::Message() << "Host: " << test.host);
-    OriginAccessEntry entry(test.protocol, test.host,
-                            OriginAccessEntry::kDisallowSubdomains);
+    OriginAccessEntry entry(
+        test.protocol, test.host, OriginAccessEntry::kDisallowSubdomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
     EXPECT_EQ(test.is_ip_address, entry.host_is_ip_address()) << test.host;
   }
 }
@@ -287,12 +297,14 @@
     SCOPED_TRACE(testing::Message()
                  << "Host: " << test.host << ", Origin: " << test.origin);
     url::Origin origin_to_test = url::Origin::Create(GURL(test.origin));
-    OriginAccessEntry entry1(test.protocol, test.host,
-                             OriginAccessEntry::kAllowSubdomains);
+    OriginAccessEntry entry1(
+        test.protocol, test.host, OriginAccessEntry::kAllowSubdomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
     EXPECT_EQ(test.expected, entry1.MatchesOrigin(origin_to_test));
 
-    OriginAccessEntry entry2(test.protocol, test.host,
-                             OriginAccessEntry::kDisallowSubdomains);
+    OriginAccessEntry entry2(
+        test.protocol, test.host, OriginAccessEntry::kDisallowSubdomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
     EXPECT_EQ(test.expected, entry2.MatchesOrigin(origin_to_test));
   }
 }
diff --git a/services/network/public/cpp/cors/origin_access_list.cc b/services/network/public/cpp/cors/origin_access_list.cc
index 36b4e4a..150d9a0 100644
--- a/services/network/public/cpp/cors/origin_access_list.cc
+++ b/services/network/public/cpp/cors/origin_access_list.cc
@@ -14,15 +14,18 @@
 void OriginAccessList::SetAllowListForOrigin(
     const url::Origin& source_origin,
     const std::vector<mojom::CorsOriginPatternPtr>& patterns) {
-  SetForOrigin(source_origin, patterns, &allow_list_);
+  SetForOrigin(source_origin, patterns, &allow_list_,
+               network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
 }
 
 void OriginAccessList::AddAllowListEntryForOrigin(
     const url::Origin& source_origin,
     const std::string& protocol,
     const std::string& domain,
-    bool allow_subdomains) {
-  AddForOrigin(source_origin, protocol, domain, allow_subdomains, &allow_list_);
+    bool allow_subdomains,
+    const network::mojom::CORSOriginAccessMatchPriority priority) {
+  AddForOrigin(source_origin, protocol, domain, allow_subdomains, &allow_list_,
+               priority);
 }
 
 void OriginAccessList::ClearAllowList() {
@@ -32,15 +35,18 @@
 void OriginAccessList::SetBlockListForOrigin(
     const url::Origin& source_origin,
     const std::vector<mojom::CorsOriginPatternPtr>& patterns) {
-  SetForOrigin(source_origin, patterns, &block_list_);
+  SetForOrigin(source_origin, patterns, &block_list_,
+               network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
 }
 
 void OriginAccessList::AddBlockListEntryForOrigin(
     const url::Origin& source_origin,
     const std::string& protocol,
     const std::string& domain,
-    bool allow_subdomains) {
-  AddForOrigin(source_origin, protocol, domain, allow_subdomains, &block_list_);
+    bool allow_subdomains,
+    const network::mojom::CORSOriginAccessMatchPriority priority) {
+  AddForOrigin(source_origin, protocol, domain, allow_subdomains, &block_list_,
+               priority);
 }
 
 void OriginAccessList::ClearBlockList() {
@@ -53,15 +59,27 @@
     return false;
   std::string source = source_origin.Serialize();
   url::Origin destination_origin = url::Origin::Create(destination);
-  return IsInMapForOrigin(source, destination_origin, allow_list_) &&
-         !IsInMapForOrigin(source, destination_origin, block_list_);
+  network::mojom::CORSOriginAccessMatchPriority allow_list_priority =
+      GetHighestPriorityOfRuleForOrigin(source, destination_origin,
+                                        allow_list_);
+  if (allow_list_priority ==
+      network::mojom::CORSOriginAccessMatchPriority::kNoMatchingOrigin)
+    return false;
+  network::mojom::CORSOriginAccessMatchPriority block_list_priority =
+      GetHighestPriorityOfRuleForOrigin(source, destination_origin,
+                                        block_list_);
+  if (block_list_priority ==
+      network::mojom::CORSOriginAccessMatchPriority::kNoMatchingOrigin)
+    return true;
+  return allow_list_priority > block_list_priority;
 }
 
 // static
 void OriginAccessList::SetForOrigin(
     const url::Origin& source_origin,
     const std::vector<mojom::CorsOriginPatternPtr>& patterns,
-    PatternMap* map) {
+    PatternMap* map,
+    const network::mojom::CORSOriginAccessMatchPriority priority) {
   DCHECK(map);
   DCHECK(!source_origin.opaque());
 
@@ -75,16 +93,19 @@
     native_patterns.push_back(OriginAccessEntry(
         pattern->protocol, pattern->domain,
         pattern->allow_subdomains ? OriginAccessEntry::kAllowSubdomains
-                                  : OriginAccessEntry::kDisallowSubdomains));
+                                  : OriginAccessEntry::kDisallowSubdomains,
+        priority));
   }
 }
 
 // static
-void OriginAccessList::AddForOrigin(const url::Origin& source_origin,
-                                    const std::string& protocol,
-                                    const std::string& domain,
-                                    bool allow_subdomains,
-                                    PatternMap* map) {
+void OriginAccessList::AddForOrigin(
+    const url::Origin& source_origin,
+    const std::string& protocol,
+    const std::string& domain,
+    bool allow_subdomains,
+    PatternMap* map,
+    const network::mojom::CORSOriginAccessMatchPriority priority) {
   DCHECK(map);
   DCHECK(!source_origin.opaque());
 
@@ -92,23 +113,30 @@
   (*map)[source].push_back(OriginAccessEntry(
       protocol, domain,
       allow_subdomains ? OriginAccessEntry::kAllowSubdomains
-                       : OriginAccessEntry::kDisallowSubdomains));
+                       : OriginAccessEntry::kDisallowSubdomains,
+      priority));
 }
 
 // static
-bool OriginAccessList::IsInMapForOrigin(const std::string& source,
-                                        const url::Origin& destination_origin,
-                                        const PatternMap& map) {
+// TODO(nrpeter): Sort OriginAccessEntry entries on edit then we can return the
+// first match which will be the top priority.
+network::mojom::CORSOriginAccessMatchPriority
+OriginAccessList::GetHighestPriorityOfRuleForOrigin(
+    const std::string& source,
+    const url::Origin& destination_origin,
+    const PatternMap& map) {
+  network::mojom::CORSOriginAccessMatchPriority highest_priority =
+      network::mojom::CORSOriginAccessMatchPriority::kNoMatchingOrigin;
   auto patterns_for_origin_it = map.find(source);
   if (patterns_for_origin_it == map.end())
-    return false;
+    return highest_priority;
   for (const auto& entry : patterns_for_origin_it->second) {
     if (entry.MatchesOrigin(destination_origin) !=
         OriginAccessEntry::kDoesNotMatchOrigin) {
-      return true;
+      highest_priority = std::max(highest_priority, entry.priority());
     }
   }
-  return false;
+  return highest_priority;
 }
 
 }  // namespace cors
diff --git a/services/network/public/cpp/cors/origin_access_list.h b/services/network/public/cpp/cors/origin_access_list.h
index 781b2367..0a8cffd 100644
--- a/services/network/public/cpp/cors/origin_access_list.h
+++ b/services/network/public/cpp/cors/origin_access_list.h
@@ -33,11 +33,14 @@
       const std::vector<mojom::CorsOriginPatternPtr>& patterns);
 
   // Adds a matching pattern for |protocol|, |domain|, and |allow_subdomains|
-  // to the allow list.
-  void AddAllowListEntryForOrigin(const url::Origin& source_origin,
-                                  const std::string& protocol,
-                                  const std::string& domain,
-                                  bool allow_subdomains);
+  // to the allow list. When two or more entries in a list match the entry
+  // with the higher |priority| takes precedence.
+  void AddAllowListEntryForOrigin(
+      const url::Origin& source_origin,
+      const std::string& protocol,
+      const std::string& domain,
+      bool allow_subdomains,
+      const network::mojom::CORSOriginAccessMatchPriority priority);
 
   // Clears the old allow list.
   void ClearAllowList();
@@ -49,11 +52,14 @@
       const std::vector<mojom::CorsOriginPatternPtr>& patterns);
 
   // Adds a matching pattern for |protocol|, |domain|, and |allow_subdomains|
-  // to the block list.
-  void AddBlockListEntryForOrigin(const url::Origin& source_origin,
-                                  const std::string& protocol,
-                                  const std::string& domain,
-                                  bool allow_subdomains);
+  // to the block list. When two or more entries in a list match the entry
+  // with the higher |priority| takes precedence.
+  void AddBlockListEntryForOrigin(
+      const url::Origin& source_origin,
+      const std::string& protocol,
+      const std::string& domain,
+      bool allow_subdomains,
+      const network::mojom::CORSOriginAccessMatchPriority priority);
 
   // Clears the old block list.
   void ClearBlockList();
@@ -70,15 +76,19 @@
   static void SetForOrigin(
       const url::Origin& source_origin,
       const std::vector<mojom::CorsOriginPatternPtr>& patterns,
-      PatternMap* map);
-  static void AddForOrigin(const url::Origin& source_origin,
-                           const std::string& protocol,
-                           const std::string& domain,
-                           bool allow_subdomains,
-                           PatternMap* map);
-  static bool IsInMapForOrigin(const std::string& source,
-                               const url::Origin& destination_origin,
-                               const PatternMap& map);
+      PatternMap* map,
+      const network::mojom::CORSOriginAccessMatchPriority priority);
+  static void AddForOrigin(
+      const url::Origin& source_origin,
+      const std::string& protocol,
+      const std::string& domain,
+      bool allow_subdomains,
+      PatternMap* map,
+      const network::mojom::CORSOriginAccessMatchPriority priority);
+  static network::mojom::CORSOriginAccessMatchPriority
+  GetHighestPriorityOfRuleForOrigin(const std::string& source,
+                                    const url::Origin& destination_origin,
+                                    const PatternMap& map);
 
   PatternMap allow_list_;
   PatternMap block_list_;
diff --git a/services/network/public/cpp/cors/origin_access_list_unittest.cc b/services/network/public/cpp/cors/origin_access_list_unittest.cc
index 8152944..7bab768 100644
--- a/services/network/public/cpp/cors/origin_access_list_unittest.cc
+++ b/services/network/public/cpp/cors/origin_access_list_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "services/network/public/cpp/cors/origin_access_list.h"
+#include "services/network/public/mojom/cors.mojom.h"
 
 #include <memory>
 
@@ -50,29 +51,35 @@
                          const std::string& host,
                          bool allow_subdomains) {
     std::vector<mojom::CorsOriginPatternPtr> patterns;
-    patterns.push_back(
-        mojom::CorsOriginPattern::New(protocol, host, allow_subdomains));
+    patterns.push_back(mojom::CorsOriginPattern::New(
+        protocol, host, allow_subdomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority));
     list_.SetAllowListForOrigin(source_origin_, patterns);
   }
-  void AddAllowListEntry(const std::string& protocol,
-                         const std::string& host,
-                         bool allow_subdomains) {
+  void AddAllowListEntry(
+      const std::string& protocol,
+      const std::string& host,
+      bool allow_subdomains,
+      const network::mojom::CORSOriginAccessMatchPriority priority) {
     list_.AddAllowListEntryForOrigin(source_origin_, protocol, host,
-                                     allow_subdomains);
+                                     allow_subdomains, priority);
   }
   void SetBlockListEntry(const std::string& protocol,
                          const std::string& host,
                          bool allow_subdomains) {
     std::vector<mojom::CorsOriginPatternPtr> patterns;
-    patterns.push_back(
-        mojom::CorsOriginPattern::New(protocol, host, allow_subdomains));
+    patterns.push_back(mojom::CorsOriginPattern::New(
+        protocol, host, allow_subdomains,
+        network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority));
     list_.SetBlockListForOrigin(source_origin_, patterns);
   }
-  void AddBlockListEntry(const std::string& protocol,
-                         const std::string& host,
-                         bool allow_subdomains) {
+  void AddBlockListEntry(
+      const std::string& protocol,
+      const std::string& host,
+      bool allow_subdomains,
+      const network::mojom::CORSOriginAccessMatchPriority priority) {
     list_.AddBlockListEntryForOrigin(source_origin_, protocol, host,
-                                     allow_subdomains);
+                                     allow_subdomains, priority);
   }
   void ResetLists() {
     std::vector<mojom::CorsOriginPatternPtr> patterns;
@@ -114,7 +121,9 @@
 
   // Adding an entry that matches subdomains should grant access to any
   // subdomains.
-  AddAllowListEntry("https", "example.com", true);
+  AddAllowListEntry(
+      "https", "example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
   EXPECT_TRUE(IsAllowed(https_example_origin()));
   EXPECT_TRUE(IsAllowed(https_sub_example_origin()));
   EXPECT_FALSE(IsAllowed(http_example_origin()));
@@ -139,12 +148,54 @@
 
 TEST_F(OriginAccessListTest, IsAccessAllowedWildcardWithBlockListEntry) {
   SetAllowListEntry("https", "", true);
-  AddBlockListEntry("https", "google.com", false);
+  AddBlockListEntry(
+      "https", "google.com", false,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
 
   EXPECT_TRUE(IsAllowed(https_example_origin()));
   EXPECT_FALSE(IsAllowed(https_google_origin()));
 }
 
+TEST_F(OriginAccessListTest, IsPriorityRespected) {
+  SetAllowListEntry("https", "example.com", true);
+  EXPECT_TRUE(IsAllowed(https_example_origin()));
+  EXPECT_TRUE(IsAllowed(https_sub_example_origin()));
+
+  // Higher priority blocklist overrides lower priority allowlist.
+  AddBlockListEntry(
+      "https", "example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+  EXPECT_FALSE(IsAllowed(https_example_origin()));
+  EXPECT_FALSE(IsAllowed(https_sub_example_origin()));
+
+  // Higher priority allowlist overrides lower priority blocklist.
+  AddAllowListEntry(
+      "https", "example.com", false,
+      network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+  EXPECT_TRUE(IsAllowed(https_example_origin()));
+  EXPECT_FALSE(IsAllowed(https_sub_example_origin()));
+}
+
+TEST_F(OriginAccessListTest, IsPriorityRespectedReverse) {
+  AddAllowListEntry(
+      "https", "example.com", false,
+      network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+  EXPECT_TRUE(IsAllowed(https_example_origin()));
+  EXPECT_FALSE(IsAllowed(https_sub_example_origin()));
+
+  AddBlockListEntry(
+      "https", "example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+  EXPECT_TRUE(IsAllowed(https_example_origin()));
+  EXPECT_FALSE(IsAllowed(https_sub_example_origin()));
+
+  AddAllowListEntry(
+      "https", "example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+  EXPECT_TRUE(IsAllowed(https_example_origin()));
+  EXPECT_FALSE(IsAllowed(https_sub_example_origin()));
+}
+
 }  // namespace
 
 }  // namespace cors
diff --git a/services/network/public/mojom/cors.mojom b/services/network/public/mojom/cors.mojom
index 0e436e2..5199351 100644
--- a/services/network/public/mojom/cors.mojom
+++ b/services/network/public/mojom/cors.mojom
@@ -95,3 +95,13 @@
   // Cross origin redirect location contains credentials such as 'user:pass'.
   kRedirectContainsCredentials,
 };
+
+// Determine which Cors exception takes precedence when multiple matches occur.
+enum CORSOriginAccessMatchPriority {
+  kNoMatchingOrigin,
+  kDefaultPriority,
+  kLowPriority,
+  kMediumPriority,
+  kHighPriority,
+  kMaxPriority
+};
diff --git a/services/network/public/mojom/cors_origin_pattern.mojom b/services/network/public/mojom/cors_origin_pattern.mojom
index 182abc8..a00e97cff 100644
--- a/services/network/public/mojom/cors_origin_pattern.mojom
+++ b/services/network/public/mojom/cors_origin_pattern.mojom
@@ -4,6 +4,8 @@
 
 module network.mojom;
 
+import "services/network/public/mojom/cors.mojom";
+
 // Parameters for representing a access origin whitelist or blacklist for CORS.
 struct CorsOriginPattern {
   // The protocol part of the destination URL.
@@ -14,4 +16,10 @@
 
   // Whether subdomains match this protocol and host pattern.
   bool allow_subdomains;
+
+  // Order of preference in which the pattern is applied.  Higher priority
+  // patterns take precedence over lower ones.  In the case were both a
+  // allow list and block list rule of the same priority match a request,
+  // the block list rule takes priority.
+  CORSOriginAccessMatchPriority priority;
 };
diff --git a/third_party/blink/public/web/web_security_policy.h b/third_party/blink/public/web/web_security_policy.h
index 6e1673c..d155e6d 100644
--- a/third_party/blink/public/web/web_security_policy.h
+++ b/third_party/blink/public/web/web_security_policy.h
@@ -31,6 +31,7 @@
 #ifndef THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_SECURITY_POLICY_H_
 #define THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_SECURITY_POLICY_H_
 
+#include "services/network/public/mojom/cors.mojom-shared.h"
 #include "third_party/blink/public/platform/web_common.h"
 #include "third_party/blink/public/platform/web_referrer_policy.h"
 
@@ -66,14 +67,32 @@
 
   // Support for managing allow/block access lists to origins beyond the
   // same-origin policy. The block list takes priority over the allow list.
+  // When an origin matches an entry on both the allow list and block list
+  // the entry with the higher priority defines whether access is allowed.
+  // In the case where both an allowlist and blocklist rule of the same
+  // priority match a request the blocklist rule takes priority.
+  // Callers should use
+  // network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority as the
+  // default priority unless overriding existing entries is explicitly needed.
   BLINK_EXPORT static void AddOriginAccessAllowListEntry(
       const WebURL& source_origin,
       const WebString& destination_protocol,
       const WebString& destination_host,
-      bool allow_destination_subdomains);
+      bool allow_destination_subdomains,
+      const network::mojom::CORSOriginAccessMatchPriority priority);
   BLINK_EXPORT static void ClearOriginAccessAllowListForOrigin(
       const WebURL& source_origin);
   BLINK_EXPORT static void ClearOriginAccessAllowList();
+  BLINK_EXPORT static void ClearOriginAccessListForOrigin(
+      const WebURL& source_origin);
+
+  BLINK_EXPORT static void AddOriginAccessBlockListEntry(
+      const WebURL& source_origin,
+      const WebString& destination_protocol,
+      const WebString& destination_host,
+      bool disallow_destination_subdomains,
+      const network::mojom::CORSOriginAccessMatchPriority priority);
+
   BLINK_EXPORT static void AddOriginAccessBlockListEntry(
       const WebURL& source_origin,
       const WebString& destination_protocol,
diff --git a/third_party/blink/renderer/core/exported/web_security_policy.cc b/third_party/blink/renderer/core/exported/web_security_policy.cc
index 4ee1bb5..9ea3356 100644
--- a/third_party/blink/renderer/core/exported/web_security_policy.cc
+++ b/third_party/blink/renderer/core/exported/web_security_policy.cc
@@ -69,10 +69,11 @@
     const WebURL& source_origin,
     const WebString& destination_protocol,
     const WebString& destination_host,
-    bool allow_destination_subdomains) {
+    bool allow_destination_subdomains,
+    const network::mojom::CORSOriginAccessMatchPriority priority) {
   SecurityPolicy::AddOriginAccessAllowListEntry(
       *SecurityOrigin::Create(source_origin), destination_protocol,
-      destination_host, allow_destination_subdomains);
+      destination_host, allow_destination_subdomains, priority);
 }
 
 void WebSecurityPolicy::ClearOriginAccessAllowListForOrigin(
@@ -85,14 +86,23 @@
   SecurityPolicy::ClearOriginAccessAllowList();
 }
 
+void WebSecurityPolicy::ClearOriginAccessListForOrigin(
+    const WebURL& source_origin) {
+  scoped_refptr<SecurityOrigin> security_origin =
+      SecurityOrigin::Create(source_origin);
+  SecurityPolicy::ClearOriginAccessAllowListForOrigin(*security_origin);
+  SecurityPolicy::ClearOriginAccessBlockListForOrigin(*security_origin);
+}
+
 void WebSecurityPolicy::AddOriginAccessBlockListEntry(
     const WebURL& source_origin,
     const WebString& destination_protocol,
     const WebString& destination_host,
-    bool allow_destination_subdomains) {
+    bool allow_destination_subdomains,
+    const network::mojom::CORSOriginAccessMatchPriority priority) {
   SecurityPolicy::AddOriginAccessBlockListEntry(
       *SecurityOrigin::Create(source_origin), destination_protocol,
-      destination_host, allow_destination_subdomains);
+      destination_host, allow_destination_subdomains, priority);
 }
 
 void WebSecurityPolicy::AddOriginTrustworthyWhiteList(const WebString& origin) {
diff --git a/third_party/blink/renderer/platform/weborigin/DEPS b/third_party/blink/renderer/platform/weborigin/DEPS
index 46fd51d..8b067e4 100644
--- a/third_party/blink/renderer/platform/weborigin/DEPS
+++ b/third_party/blink/renderer/platform/weborigin/DEPS
@@ -11,6 +11,7 @@
     "+net/base",
     "+services/network/public/cpp/cors/origin_access_entry.h",
     "+services/network/public/cpp/cors/origin_access_list.h",
+    "+services/network/public/mojom/cors.mojom-shared.h",
     "+services/network/public/mojom/cors_origin_pattern.mojom-shared.h",
     "+third_party/blink/renderer/platform/blob/blob_url.h",
     "+third_party/blink/renderer/platform/platform_export.h",
diff --git a/third_party/blink/renderer/platform/weborigin/origin_access_entry.cc b/third_party/blink/renderer/platform/weborigin/origin_access_entry.cc
index 9e729629..bba1484e 100644
--- a/third_party/blink/renderer/platform/weborigin/origin_access_entry.cc
+++ b/third_party/blink/renderer/platform/weborigin/origin_access_entry.cc
@@ -30,6 +30,7 @@
 
 #include "third_party/blink/renderer/platform/weborigin/origin_access_entry.h"
 
+#include "services/network/public/mojom/cors.mojom-shared.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 
 namespace blink {
@@ -37,10 +38,12 @@
 OriginAccessEntry::OriginAccessEntry(
     const String& protocol,
     const String& host,
-    network::cors::OriginAccessEntry::MatchMode match_mode)
+    network::cors::OriginAccessEntry::MatchMode match_mode,
+    network::mojom::CORSOriginAccessMatchPriority priority)
     : private_(std::string(protocol.Utf8().data()),
                std::string(host.Utf8().data()),
-               match_mode) {}
+               match_mode,
+               priority) {}
 
 OriginAccessEntry::OriginAccessEntry(OriginAccessEntry&& from) = default;
 
diff --git a/third_party/blink/renderer/platform/weborigin/origin_access_entry.h b/third_party/blink/renderer/platform/weborigin/origin_access_entry.h
index 037cae5..7b7ae44 100644
--- a/third_party/blink/renderer/platform/weborigin/origin_access_entry.h
+++ b/third_party/blink/renderer/platform/weborigin/origin_access_entry.h
@@ -49,9 +49,14 @@
   // will match all domains in the specified protocol.
   // IPv6 addresses must include brackets (e.g.
   // '[2001:db8:85a3::8a2e:370:7334]', not '2001:db8:85a3::8a2e:370:7334').
-  OriginAccessEntry(const String& protocol,
-                    const String& host,
-                    network::cors::OriginAccessEntry::MatchMode);
+  // An entry with a higher priority will win in case there are two conflicting
+  // entries.
+  OriginAccessEntry(
+      const String& protocol,
+      const String& host,
+      network::cors::OriginAccessEntry::MatchMode,
+      network::mojom::CORSOriginAccessMatchPriority priority =
+          network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
   OriginAccessEntry(OriginAccessEntry&& from);
 
   // 'matchesOrigin' requires a protocol match (e.g. 'http' != 'https').
diff --git a/third_party/blink/renderer/platform/weborigin/security_origin_test.cc b/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
index a7c8a3f..3604648 100644
--- a/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
@@ -32,6 +32,7 @@
 
 #include <stdint.h>
 
+#include "services/network/public/mojom/cors.mojom-shared.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/platform/blob/blob_url.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
@@ -348,18 +349,110 @@
   }
 }
 
-TEST_F(SecurityOriginTest, CanRequestWithWhitelistedAccess) {
+TEST_F(SecurityOriginTest, CanRequestWithAllowListedAccess) {
   scoped_refptr<const SecurityOrigin> origin =
       SecurityOrigin::CreateFromString("https://chromium.org");
   const blink::KURL url("https://example.com");
 
   EXPECT_FALSE(origin->CanRequest(url));
-  // Adding the url to the access whitelist should allow the request.
-  SecurityPolicy::AddOriginAccessAllowListEntry(*origin, "https", "example.com",
-                                                false);
+  // Adding the url to the access allowlist should allow the request.
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *origin, "https", "example.com", false,
+      network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
   EXPECT_TRUE(origin->CanRequest(url));
 }
 
+TEST_F(SecurityOriginTest, CannotRequestWithBlockListedAccess) {
+  scoped_refptr<const SecurityOrigin> origin =
+      SecurityOrigin::CreateFromString("https://chromium.org");
+  const blink::KURL allowed_url("https://test.example.com");
+  const blink::KURL blocked_url("https://example.com");
+
+  // BlockList that is more or same specificity wins.
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *origin, "https", "example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+  SecurityPolicy::AddOriginAccessBlockListEntry(
+      *origin, "https", "example.com", false,
+      network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+  // Block since example.com is on the allowlist & blocklist.
+  EXPECT_FALSE(origin->CanRequest(blocked_url));
+  // Allow since *.example.com is on the allowlist but not the blocklist.
+  EXPECT_TRUE(origin->CanRequest(allowed_url));
+}
+
+TEST_F(SecurityOriginTest, CanRequestWithMoreSpecificAllowList) {
+  scoped_refptr<const SecurityOrigin> origin =
+      SecurityOrigin::CreateFromString("https://chromium.org");
+  const blink::KURL allowed_url("https://test.example.com");
+  const blink::KURL blocked_url("https://example.com");
+
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *origin, "https", "test.example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+  SecurityPolicy::AddOriginAccessBlockListEntry(
+      *origin, "https", "example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+  // Allow since test.example.com (allowlist) has a higher priority than
+  // *.example.com (blocklist).
+  EXPECT_TRUE(origin->CanRequest(allowed_url));
+  // Block since example.com isn't on the allowlist.
+  EXPECT_FALSE(origin->CanRequest(blocked_url));
+}
+
+TEST_F(SecurityOriginTest, PunycodeNotUnicode) {
+  scoped_refptr<const SecurityOrigin> origin =
+      SecurityOrigin::CreateFromString("https://chromium.org");
+  const blink::KURL unicode_url("https://☃.net/");
+  const blink::KURL punycode_url("https://xn--n3h.net/");
+
+  // Sanity check: Origin blocked by default.
+  EXPECT_FALSE(origin->CanRequest(punycode_url));
+  EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+  // Verify unicode origin can not be allowlisted.
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *origin, "https", "☃.net", true,
+      network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+  EXPECT_FALSE(origin->CanRequest(punycode_url));
+  EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+  // Verify punycode allowlist only affects punycode URLs.
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *origin, "https", "xn--n3h.net", true,
+      network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+  EXPECT_TRUE(origin->CanRequest(punycode_url));
+  EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+  // Clear enterprise policy allowlist.
+  SecurityPolicy::ClearOriginAccessAllowListForOrigin(*origin);
+
+  EXPECT_FALSE(origin->CanRequest(punycode_url));
+  EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+  // Simulate <all_urls> being in the extension permissions.
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *origin, "https", "", true,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+
+  EXPECT_TRUE(origin->CanRequest(punycode_url));
+  EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+  // Verify unicode origin can not be blocklisted.
+  SecurityPolicy::AddOriginAccessBlockListEntry(
+      *origin, "https", "☃.net", true,
+      network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+  EXPECT_TRUE(origin->CanRequest(punycode_url));
+  EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+  // Verify punycode blocklist only affects punycode URLs.
+  SecurityPolicy::AddOriginAccessBlockListEntry(
+      *origin, "https", "xn--n3h.net", true,
+      network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+  EXPECT_FALSE(origin->CanRequest(punycode_url));
+  EXPECT_FALSE(origin->CanRequest(unicode_url));
+}
+
 TEST_F(SecurityOriginTest, PortAndEffectivePortMethod) {
   struct TestCase {
     unsigned short port;
diff --git a/third_party/blink/renderer/platform/weborigin/security_policy.cc b/third_party/blink/renderer/platform/weborigin/security_policy.cc
index 5374e03..1aef3f2 100644
--- a/third_party/blink/renderer/platform/weborigin/security_policy.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_policy.cc
@@ -239,11 +239,13 @@
     const SecurityOrigin& source_origin,
     const String& destination_protocol,
     const String& destination_domain,
-    bool allow_destination_subdomains) {
+    bool allow_destination_subdomains,
+    const network::mojom::CORSOriginAccessMatchPriority priority) {
   MutexLocker lock(GetMutex());
   GetOriginAccessList().AddAllowListEntryForOrigin(
       source_origin.ToUrlOrigin(), WebString(destination_protocol).Utf8(),
-      WebString(destination_domain).Utf8(), allow_destination_subdomains);
+      WebString(destination_domain).Utf8(), allow_destination_subdomains,
+      priority);
 }
 
 void SecurityPolicy::ClearOriginAccessAllowListForOrigin(
@@ -254,6 +256,14 @@
       std::vector<network::mojom::CorsOriginPatternPtr>());
 }
 
+void SecurityPolicy::ClearOriginAccessBlockListForOrigin(
+    const SecurityOrigin& source_origin) {
+  MutexLocker lock(GetMutex());
+  GetOriginAccessList().SetBlockListForOrigin(
+      source_origin.ToUrlOrigin(),
+      std::vector<network::mojom::CorsOriginPatternPtr>());
+}
+
 void SecurityPolicy::ClearOriginAccessAllowList() {
   MutexLocker lock(GetMutex());
   GetOriginAccessList().ClearAllowList();
@@ -263,11 +273,13 @@
     const SecurityOrigin& source_origin,
     const String& destination_protocol,
     const String& destination_domain,
-    bool allow_destination_subdomains) {
+    bool allow_destination_subdomains,
+    const network::mojom::CORSOriginAccessMatchPriority priority) {
   MutexLocker lock(GetMutex());
   GetOriginAccessList().AddBlockListEntryForOrigin(
       source_origin.ToUrlOrigin(), WebString(destination_protocol).Utf8(),
-      WebString(destination_domain).Utf8(), allow_destination_subdomains);
+      WebString(destination_domain).Utf8(), allow_destination_subdomains,
+      priority);
 }
 
 void SecurityPolicy::ClearOriginAccessBlockList() {
diff --git a/third_party/blink/renderer/platform/weborigin/security_policy.h b/third_party/blink/renderer/platform/weborigin/security_policy.h
index 5cf330b..68aea8f 100644
--- a/third_party/blink/renderer/platform/weborigin/security_policy.h
+++ b/third_party/blink/renderer/platform/weborigin/security_policy.h
@@ -29,6 +29,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_WEBORIGIN_SECURITY_POLICY_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_WEBORIGIN_SECURITY_POLICY_H_
 
+#include "services/network/public/mojom/cors.mojom-shared.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/weborigin/referrer.h"
 #include "third_party/blink/renderer/platform/weborigin/referrer_policy.h"
@@ -65,18 +66,24 @@
                                    const KURL&,
                                    const String& referrer);
 
-  static void AddOriginAccessAllowListEntry(const SecurityOrigin& source_origin,
-                                            const String& destination_protocol,
-                                            const String& destination_domain,
-                                            bool allow_destination_subdomains);
+  static void AddOriginAccessAllowListEntry(
+      const SecurityOrigin& source_origin,
+      const String& destination_protocol,
+      const String& destination_domain,
+      bool allow_destination_subdomains,
+      const network::mojom::CORSOriginAccessMatchPriority priority);
   static void ClearOriginAccessAllowListForOrigin(
       const SecurityOrigin& source_origin);
+  static void ClearOriginAccessBlockListForOrigin(
+      const SecurityOrigin& source_origin);
   static void ClearOriginAccessAllowList();
 
-  static void AddOriginAccessBlockListEntry(const SecurityOrigin& source_origin,
-                                            const String& destination_protocol,
-                                            const String& destination_domain,
-                                            bool allow_destination_subdomains);
+  static void AddOriginAccessBlockListEntry(
+      const SecurityOrigin& source_origin,
+      const String& destination_protocol,
+      const String& destination_domain,
+      bool allow_destination_subdomains,
+      const network::mojom::CORSOriginAccessMatchPriority priority);
   static void ClearOriginAccessBlockList();
 
   static bool IsOriginAccessAllowed(const SecurityOrigin* active_origin,
diff --git a/third_party/blink/renderer/platform/weborigin/security_policy_test.cc b/third_party/blink/renderer/platform/weborigin/security_policy_test.cc
index e38c0b5..6faa417 100644
--- a/third_party/blink/renderer/platform/weborigin/security_policy_test.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_policy_test.cc
@@ -30,6 +30,7 @@
 
 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
 
+#include "services/network/public/mojom/cors.mojom-shared.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
@@ -349,8 +350,9 @@
 
   // Adding access for https://example.com should work, but should not grant
   // access to subdomains or other schemes.
-  SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
-                                                "https", "example.com", false);
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *https_chromium_origin(), "https", "example.com", false,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
   EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
                                                     https_example_origin()));
   EXPECT_FALSE(SecurityPolicy::IsOriginAccessAllowed(
@@ -369,8 +371,9 @@
 
   // Adding an entry that matches subdomains should grant access to any
   // subdomains.
-  SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
-                                                "https", "example.com", true);
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *https_chromium_origin(), "https", "example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
   EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
                                                     https_example_origin()));
   EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(
@@ -381,8 +384,9 @@
 
 TEST_F(SecurityPolicyAccessTest, IsOriginAccessAllowedWildCard) {
   // An empty domain that matches subdomains results in matching every domain.
-  SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
-                                                "https", "", true);
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *https_chromium_origin(), "https", "", true,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
   EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
                                                     https_example_origin()));
   EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
@@ -393,10 +397,12 @@
 
 TEST_F(SecurityPolicyAccessTest, IsOriginAccessAllowedWithBlockListEntry) {
   // The block list takes priority over the allow list.
-  SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
-                                                "https", "example.com", true);
-  SecurityPolicy::AddOriginAccessBlockListEntry(*https_chromium_origin(),
-                                                "https", "example.com", false);
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *https_chromium_origin(), "https", "example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+  SecurityPolicy::AddOriginAccessBlockListEntry(
+      *https_chromium_origin(), "https", "example.com", false,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
 
   EXPECT_FALSE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
                                                      https_example_origin()));
@@ -406,10 +412,12 @@
 
 TEST_F(SecurityPolicyAccessTest,
        IsOriginAccessAllowedWildcardWithBlockListEntry) {
-  SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
-                                                "https", "", true);
-  SecurityPolicy::AddOriginAccessBlockListEntry(*https_chromium_origin(),
-                                                "https", "google.com", false);
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *https_chromium_origin(), "https", "", true,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+  SecurityPolicy::AddOriginAccessBlockListEntry(
+      *https_chromium_origin(), "https", "google.com", false,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
 
   EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
                                                     https_example_origin()));
@@ -418,12 +426,15 @@
 }
 
 TEST_F(SecurityPolicyAccessTest, ClearOriginAccessAllowListForOrigin) {
-  SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
-                                                "https", "example.com", true);
-  SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
-                                                "https", "google.com", true);
-  SecurityPolicy::AddOriginAccessAllowListEntry(*https_example_origin(),
-                                                "https", "google.com", true);
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *https_chromium_origin(), "https", "example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *https_chromium_origin(), "https", "google.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *https_example_origin(), "https", "google.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
 
   SecurityPolicy::ClearOriginAccessAllowListForOrigin(*https_chromium_origin());
 
@@ -435,4 +446,25 @@
                                                     https_google_origin()));
 }
 
+TEST_F(SecurityPolicyAccessTest, IsOriginAccessAllowedPriority) {
+  EXPECT_FALSE(SecurityPolicy::IsOriginAccessAllowed(
+      https_chromium_origin(), https_sub_example_origin()));
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *https_chromium_origin(), "https", "sub.example.com", false,
+      network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+  EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(
+      https_chromium_origin(), https_sub_example_origin()));
+  SecurityPolicy::AddOriginAccessBlockListEntry(
+      *https_chromium_origin(), "https", "example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+  EXPECT_FALSE(SecurityPolicy::IsOriginAccessAllowed(
+      https_chromium_origin(), https_sub_example_origin()));
+  SecurityPolicy::AddOriginAccessAllowListEntry(
+      *https_chromium_origin(), "https", "example.com", true,
+      network::mojom::CORSOriginAccessMatchPriority::kHighPriority);
+
+  EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(
+      https_chromium_origin(), https_sub_example_origin()));
+}
+
 }  // namespace blink