Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Extensions
DescriptionIncorrect authorization in Extensions
ComponentExtensions
Bug ClassLogic Error
Tracker543938457
Fix commit6813284e59b3 (chromium/src) +106/-6
CISA KEVNot listed
Creditedantoniosmr02
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
if
chrome/browser/extensions/api/cookies/cookies_api.cc
modified
if
chrome/browser/extensions/api/cookies/cookies_unittest.cc
modified
TEST_F
chrome/browser/extensions/api/cookies/cookies_unittest.cc
modified

Files Changed

  • chrome/browser/extensions/api/cookies/cookies_api.cc
  • chrome/browser/extensions/api/cookies/cookies_helpers.cc
  • chrome/browser/extensions/api/cookies/cookies_unittest.cc
From 6813284e59b397be04f5aadd5f4b9ffe87edddff Mon Sep 17 00:00:00 2001
From: Eva Su <[email protected]>
Date: Fri, 14 Aug 2026 18:17:01 -0700
Subject: [PATCH] [Extensions] Enforce user site restrictions in chrome.cookies API

Previously, the chrome.cookies API verified host access permissions
using PermissionsData::HasHostPermission(url) which checks explicit
manifest host permissions and enterprise policy host blocks, but does
not check user host restrictions or withheld permissions.

So when a user explicitly turns off extensions for a site or withholds
host permissions, an extension with host permissions could continue
using chrome.cookies methods to read, create, and remove cookies for
that domain.

This CL replaces the HasHostPermission check with GetPageAccess which is
a superset of what HasHostPermission checks that also validates user
site restrictions, withheld permissions, enterprise policy, and
restricted URLs.

Fixed: 543938457
Change-Id: Ic46119d4da0bb32b84c3c3e90eec696c110a2e16
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8259390
Reviewed-by: Solomon Kinard <[email protected]>
Commit-Queue: Eva Su <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1680073}
---

diff --git a/chrome/browser/extensions/api/cookies/cookies_api.cc b/chrome/browser/extensions/api/cookies/cookies_api.cc
index b605d852..46d1698f 100644
--- a/chrome/browser/extensions/api/cookies/cookies_api.cc
+++ b/chrome/browser/extensions/api/cookies/cookies_api.cc
@@ -28,6 +28,7 @@
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_api_frame_id_map.h"
 #include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/constants.h"
 #include "extensions/common/error_utils.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/permissions/permissions_data.h"
@@ -74,9 +75,16 @@
 bool CheckHostPermissions(const Extension* extension,
                           const GURL& url,
                           std::string* error) {
-  if (!extension->permissions_data()->HasHostPermission(url)) {
-    *error =
-        ErrorUtils::FormatErrorMessage(kNoHostPermissionsError, url.spec());
+  // Cookie operations are profile-scoped and not tied to a specific tab
+  // context so we pass kUnknownTabId to check page access without considering
+  // tab-specific grants.
+  if (extension->permissions_data()->GetPageAccess(
+          url, extension_misc::kUnknownTabId, /*error=*/nullptr) !=
+      PermissionsData::PageAccess::kAllowed) {
+    if (error) {
+      *error =
+          ErrorUtils::FormatErrorMessage(kNoHostPermissionsError, url.spec());
+    }
     return false;
   }
   return true;
diff --git a/chrome/browser/extensions/api/cookies/cookies_helpers.cc b/chrome/browser/extensions/api/cookies/cookies_helpers.cc
index 911552d..fa5319f 100644
--- a/chrome/browser/extensions/api/cookies/cookies_helpers.cc
+++ b/chrome/browser/extensions/api/cookies/cookies_helpers.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/cookies.h"
 #include "content/public/browser/web_contents.h"
+#include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
@@ -47,10 +48,13 @@
     const Extension* extension,
     std::vector<Cookie>* match_vector,
     const net::CookiePartitionKeyCollection& cookie_partition_key_collection) {
-  // Ignore any cookie whose domain doesn't match the extension's
-  // host permissions.
+  // Ignore any cookie whose domain doesn't match the extension's page access
+  // controls. Pass kUnknownTabId because cookie operations are profile-wide
+  // rather than tab-bound.
   GURL cookie_domain_url = cookies_helpers::GetURLFromCanonicalCookie(cookie);
-  if (!extension->permissions_data()->HasHostPermission(cookie_domain_url)) {
+  if (extension->permissions_data()->GetPageAccess(
+          cookie_domain_url, extension_misc::kUnknownTabId, nullptr) !=
+      PermissionsData::PageAccess::kAllowed) {
     return;
   }
   // Filter the cookie using the match filter.
diff --git a/chrome/browser/extensions/api/cookies/cookies_unittest.cc b/chrome/browser/extensions/api/cookies/cookies_unittest.cc
index 2f8a254..526d102 100644
--- a/chrome/browser/extensions/api/cookies/cookies_unittest.cc
+++ b/chrome/browser/extensions/api/cookies/cookies_unittest.cc
@@ -15,6 +15,7 @@
 #include <vector>
 
 #include "base/test/gtest_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/extensions/api/cookies/cookies_helpers.h"
 #include "chrome/common/extensions/api/cookies.h"
@@ -22,6 +23,9 @@
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
 #include "extensions/buildflags/buildflags.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_features.h"
+#include "extensions/common/permissions/permissions_data.h"
 #include "net/cookies/canonical_cookie.h"
 #include "net/cookies/cookie_constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -305,4 +309,88 @@
   EXPECT_CHECK_DEATH(cookies_helpers::CreateCookie(*opaque_cookie, "0"));
 }
 
+namespace {
+
+std::vector<Cookie> GetMatchingCookiesForExtension(
+    const Extension* extension,
+    const std::string& domain = "example.com") {
+  auto cookie = net::CanonicalCookie::CreateUnsafeCookieForTesting(
+      "ABC", "DEF", domain, "/", base::Time(), base::Time(), base::Time(),
+      base::Time(), false, false, net::CookieSameSite::NO_RESTRICTION,
+      net::COOKIE_PRIORITY_DEFAULT, net::CookieSourceType::kOther);
+  if (!cookie) {
+    return {};
+  }
+
+  base::DictValue dict;
+  dict.Set("storeId", "0");
+  auto details = GetAll::Params::Details::FromValue(dict);
+  if (!details) {
+    return {};
+  }
+
+  std::vector<Cookie> match_vector;
+  cookies_helpers::AppendMatchingCookiesFromCookieListToVector(
+      {*cookie}, &details.value(), extension, &match_vector,
+      net::CookiePartitionKeyCollection());
+  return match_vector;
+}
+
+}  // namespace
+
+// Ensures cookies from domains explicitly blocked by the user are excluded from
+// matches.
+TEST_F(ExtensionCookiesTest, AppendMatchingCookiesWithUserBlockedSite) {
+  base::test::ScopedFeatureList feature_list(
+      extensions_features::kExtensionsMenuAccessControl);
+
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("Test Extension")
+          .AddHostPermission("<all_urls>")
+          .Build();
+
+  constexpr int kContextId = 1;
+  extension->permissions_data()->SetContextId(kContextId);
+
+  // Mark example.com as user-blocked.
+  URLPatternSet user_blocked_hosts;
+  URLPattern pattern(URLPattern::SCHEME_ALL, "*://*.example.com/*");
+  user_blocked_hosts.AddPattern(pattern);
+  PermissionsData::SetUserHostRestrictions(kContextId,
+                                           std::move(user_blocked_hosts),
+                                           /*user_allowed_hosts=*/{});
+
+  // Since example.com is user-blocked, the cookie must not be matched.
+  EXPECT_TRUE(GetMatchingCookiesForExtension(extension.get()).empty());
+}
+
+// Ensures cookies from domains with withheld host permissions are excluded from
+// matches.
+TEST_F(ExtensionCookiesTest, AppendMatchingCookiesWithWithheldPermissions) {
+  base::test::ScopedFeatureList feature_list(
+      extensions_features::kExtensionsMenuAccessControl);
+
+  base::ListValue host_permissions;
+  host_permissions.Append("*://*.example.com/*");
+
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("Test Extension")
+          .SetManifestKey("host_permissions", std::move(host_permissions))
+          .Build();
+
+  // Withhold the host permission from the extension.
+  URLPatternSet withheld_hosts;
+  withheld_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_ALL, "*://*.example.com/*"));
+
+  extension->permissions_data()->SetPermissions(
+      std::make_unique<PermissionSet>(),
+      std::make_unique<PermissionSet>(APIPermissionSet(),
+                                      ManifestPermissionSet(),
+                                      withheld_hosts.Clone(), URLPatternSet()));
+
+  // Since the permission is withheld, the cookie must not be matched.
+  EXPECT_TRUE(GetMatchingCookiesForExtension(extension.get()).empty());
+}
+
 }  // namespace extensions
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/extensions/api/cookies/cookies_unittest.cc b/chrome/browser/extensions/api/cookies/cookies_unittest.cc
index 2f8a254..526d102 100644
--- a/chrome/browser/extensions/api/cookies/cookies_unittest.cc
+++ b/chrome/browser/extensions/api/cookies/cookies_unittest.cc
@@ -15,6 +15,7 @@
 #include <vector>
 
 #include "base/test/gtest_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/extensions/api/cookies/cookies_helpers.h"
 #include "chrome/common/extensions/api/cookies.h"
@@ -22,6 +23,9 @@
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
 #include "extensions/buildflags/buildflags.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_features.h"
+#include "extensions/common/permissions/permissions_data.h"
 #include "net/cookies/canonical_cookie.h"
 #include "net/cookies/cookie_constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -305,4 +309,88 @@
   EXPECT_CHECK_DEATH(cookies_helpers::CreateCookie(*opaque_cookie, "0"));
 }
 
+namespace {
+
+std::vector<Cookie> GetMatchingCookiesForExtension(
+    const Extension* extension,
+    const std::string& domain = "example.com") {
+  auto cookie = net::CanonicalCookie::CreateUnsafeCookieForTesting(
+      "ABC", "DEF", domain, "/", base::Time(), base::Time(), base::Time(),
+      base::Time(), false, false, net::CookieSameSite::NO_RESTRICTION,
+      net::COOKIE_PRIORITY_DEFAULT, net::CookieSourceType::kOther);
+  if (!cookie) {
+    return {};
+  }
+
+  base::DictValue dict;
+  dict.Set("storeId", "0");
+  auto details = GetAll::Params::Details::FromValue(dict);
+  if (!details) {
+    return {};
+  }
+
+  std::vector<Cookie> match_vector;
+  cookies_helpers::AppendMatchingCookiesFromCookieListToVector(
+      {*cookie}, &details.value(), extension, &match_vector,
+      net::CookiePartitionKeyCollection());
+  return match_vector;
+}
+
+}  // namespace
+
+// Ensures cookies from domains explicitly blocked by the user are excluded from
+// matches.
+TEST_F(ExtensionCookiesTest, AppendMatchingCookiesWithUserBlockedSite) {
+  base::test::ScopedFeatureList feature_list(
+      extensions_features::kExtensionsMenuAccessControl);
+
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("Test Extension")
+          .AddHostPermission("<all_urls>")
+          .Build();
+
+  constexpr int kContextId = 1;
+  extension->permissions_data()->SetContextId(kContextId);
+
+  // Mark example.com as user-blocked.
+  URLPatternSet user_blocked_hosts;
+  URLPattern pattern(URLPattern::SCHEME_ALL, "*://*.example.com/*");
+  user_blocked_hosts.AddPattern(pattern);
+  PermissionsData::SetUserHostRestrictions(kContextId,
+                                           std::move(user_blocked_hosts),
+                                           /*user_allowed_hosts=*/{});
+
+  // Since example.com is user-blocked, the cookie must not be matched.
+  EXPECT_TRUE(GetMatchingCookiesForExtension(extension.get()).empty());
+}
+
+// Ensures cookies from domains with withheld host permissions are excluded from
+// matches.
+TEST_F(ExtensionCookiesTest, AppendMatchingCookiesWithWithheldPermissions) {
+  base::test::ScopedFeatureList feature_list(
+      extensions_features::kExtensionsMenuAccessControl);
+
+  base::ListValue host_permissions;
+  host_permissions.Append("*://*.example.com/*");
+
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("Test Extension")
+          .SetManifestKey("host_permissions", std::move(host_permissions))
+          .Build();
+
+  // Withhold the host permission from the extension.
+  URLPatternSet withheld_hosts;
+  withheld_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_ALL, "*://*.example.com/*"));
+
+  extension->permissions_data()->SetPermissions(
+      std::make_unique<PermissionSet>(),
+      std::make_unique<PermissionSet>(APIPermissionSet(),
+                                      ManifestPermissionSet(),
+                                      withheld_hosts.Clone(), URLPatternSet()));
+
+  // Since the permission is withheld, the cookie must not be matched.
+  EXPECT_TRUE(GetMatchingCookiesForExtension(extension.get()).empty());
+}
+
 }  // namespace extensions
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.