Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Extensions
DescriptionIncorrect authorization in Extensions
ComponentExtensions
Bug ClassLogic Error
Tracker542355360
Fix commit2a11198c47e9 (chromium/src) +391/-22
CISA KEVNot listed
Creditedantoniosmr02
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
IN_PROC_BROWSER_TEST_F
chrome/browser/extensions/sandboxed_pages_apitest.cc
modified

Files Changed

  • chrome/browser/extensions/sandboxed_pages_apitest.cc
  • chrome/browser/extensions/web_accessible_resources_browsertest.cc
From 2a11198c47e96470dcca19b629a3a186d710c847 Mon Sep 17 00:00:00 2001
From: Devlin Cronin <[email protected]>
Date: Thu, 13 Aug 2026 11:33:10 -0700
Subject: [PATCH] [Extensions] Sandbox pages based on case-insensitive matching

There's a bug where if an extension sandboxes "sandboxed.html", but a
frame loads "Sandboxed.html", that frame won't be sandboxed (because it
doesn't match the pattern). On case-sensitive file systems (like linux),
this is fine: Sandboxed.html won't load. However, on case-insensitive
file systems (like Windows or most Mac systems), this is problematic:
Sandboxed.html will load sandboxed.html, but the frame won't be
sandboxed.

The *best* fix for this is likely to align our resource loading with the
canonical paths on the file system such that if there's a request for
Path.html, it will only succeed if there is a resource with the
(case-sensitive) Path.html path on disk. This would fix this issue and
would also ensure that developers don't accidentally have broken
extensions on case-sensitive file systems.

Unfortunately, we don't have a good way of knowing if that would break
existing extensions, since some extensions may be (improperly) relying
on this behavior, trying to load Path.html when the file is path.html.

In order to fix the sandboxing issue without risking breaking
extensions, make whether to sandbox a resource based on case-insensitive
matching: that is, if the extension specifies sandboxed.html, a frame
that loads Sandboxed.html will also be sandboxed. (Technically, this
also has a risk of breakage: an extension could have sandboxed.html AND
Sandboxed.html and only want to sandbox one of them. This is
significantly less likely than the other breaking flow, though.)

Do this by introducing a "case-sensitive" parameter to
Extension::ResourceMatches() and URLPattern[Set]::MatchesURL().

In addition to the case-sensitivity, we also introduce checks for
unicode escaping (and its own case-sensitivity).

Do *not* use this logic to apply to other uses of
Extension::ResourceMatches(), in particular, web-accessible resources.
This introduces an inconsistency; however, this allows us to default to
the more secure option in both cases: when in doubt, we sandbox the
frame, and when in doubt, we *don't* allow the frame in untrusted
contexts.

Longer term, we should measure and revisit simply changing our protocols
to only load the canonical path in the extension, at which point we
could remove this (since attempting to load a frame that doesn't match
the casing of the resource would fail).

Bug: 542355360, 545512660
Change-Id: Ib17f22c18e178c3fc6b701bd98c3be5f92718ef5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8242131
Reviewed-by: Andrea Orru <[email protected]>
Commit-Queue: Devlin Cronin <[email protected]>
Auto-Submit: Devlin Cronin <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1678984}
---

diff --git a/chrome/browser/extensions/sandboxed_pages_apitest.cc b/chrome/browser/extensions/sandboxed_pages_apitest.cc
index e5beb22..731679e4 100644
--- a/chrome/browser/extensions/sandboxed_pages_apitest.cc
+++ b/chrome/browser/extensions/sandboxed_pages_apitest.cc
@@ -556,6 +556,83 @@
       extension->id(), frame_host->GetProcess()->GetID()));
 }
 
+// Verifies that requesting a sandboxed page using case-variant path (e.g.
+// "Sandboxed.html") is still recognized as a sandboxed page.
+// Regression test for https://crbug.com/542355360.
+IN_PROC_BROWSER_TEST_F(SandboxedPagesTest, CaseInsensitiveSandboxedPagePath) {
+  static constexpr char kManifest[] =
+      R"({
+           "name": "Case-insensitive sandboxed page test",
+           "version": "0.1",
+           "manifest_version": 3,
+           "sandbox": { "pages": ["sandboxed.html", "café.html"] }
+         })";
+  static constexpr char kSandboxedHtml[] =
+      R"(<html><body>Sandboxed Page</body></html>)";
+  static constexpr char kCafeHtml[] =
+      R"(<html>
+           <head><meta charset="utf-8"></head><body>Café Page</body>
+         </html>)";
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  // Write the case-variant files first so that on case-sensitive filesystems
+  // (like Linux) the files exist to be loaded, but on case-insensitive
+  // filesystems (macOS, Windows) the canonical lowercase files are written
+  // second.
+  test_dir.WriteFile(FILE_PATH_LITERAL("Sandboxed.html"), kSandboxedHtml);
+  test_dir.WriteFile(FILE_PATH_LITERAL("sandboxed.html"), kSandboxedHtml);
+  test_dir.WriteFile(FILE_PATH_LITERAL("CAFÉ.html"), kCafeHtml);
+  test_dir.WriteFile(FILE_PATH_LITERAL("café.html"), kCafeHtml);
+
+  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  content::WebContents* web_contents = GetActiveWebContents();
+
+  // Test ASCII case mismatch.
+  {
+    // Note: Deliberately don't use Extension::GetResourceURL(), which goes
+    // through additional sanitization checks.
+    GURL uppercase_url = extension->url().Resolve("Sandboxed.html");
+    ASSERT_TRUE(NavigateToURL(web_contents, uppercase_url));
+
+    // The page should load, but should be properly sandboxed: it shouldn't
+    // have extension APIs or be hosted in a trusted process.
+    content::RenderFrameHost* frame_host = web_contents->GetPrimaryMainFrame();
+    ASSERT_TRUE(frame_host);
+    EXPECT_FALSE(frame_host->IsErrorDocument());
+    EXPECT_EQ("Sandboxed Page",
+              content::EvalJs(web_contents, "document.body.innerText"));
+    EXPECT_EQ("null", frame_host->GetLastCommittedOrigin().Serialize());
+    EXPECT_EQ("undefined",
+              content::EvalJs(web_contents, "typeof chrome.runtime"));
+    EXPECT_FALSE(ProcessMap::Get(profile())->Contains(
+        extension->id(), frame_host->GetProcess()->GetID()));
+  }
+
+  // Test non-ASCII / Unicode UTF-8 case mismatch.
+  {
+    // Note: Deliberately don't use Extension::GetResourceURL(), which goes
+    // through additional sanitization checks.
+    GURL uppercase_url = extension->url().Resolve("CAFÉ.html");
+    ASSERT_TRUE(NavigateToURL(web_contents, uppercase_url));
+
+    // The page should load, but should be properly sandboxed: it shouldn't
+    // have extension APIs or be hosted in a trusted process.
+    content::RenderFrameHost* frame_host = web_contents->GetPrimaryMainFrame();
+    ASSERT_TRUE(frame_host);
+    EXPECT_FALSE(frame_host->IsErrorDocument());
+    EXPECT_EQ("Café Page",
+              content::EvalJs(web_contents, "document.body.innerText"));
+    EXPECT_EQ("null", frame_host->GetLastCommittedOrigin().Serialize());
+    EXPECT_EQ("undefined",
+              content::EvalJs(web_contents, "typeof chrome.runtime"));
+    EXPECT_FALSE(ProcessMap::Get(profile())->Contains(
+        extension->id(), frame_host->GetProcess()->GetID()));
+  }
+}
+
 // Pages that are sandboxed with the HTML5 `sandbox` attribute are treated
 // differently from pages specified in the "sandbox" attribute in the manifest.
 // These pages *do* get extension APIs.
diff --git a/chrome/browser/extensions/web_accessible_resources_browsertest.cc b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
index f6861b432..c0df203 100644
--- a/chrome/browser/extensions/web_accessible_resources_browsertest.cc
+++ b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
@@ -129,6 +129,121 @@
   ASSERT_TRUE(content::EvalJs(web_contents, script).ExtractBool());
 }
 
+// Verifies that web accessible resource matching is case-sensitive, so
+// requests using case-variant paths to allowed resources are blocked, and
+// unlisted resources remain inaccessible.
+IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesBrowserTest,
+                       WebAccessibleResourcesAreCaseSensitive) {
+  static constexpr char kManifest[] = R"({
+    "name": "Case Sensitive WAR Test",
+    "version": "0.1",
+    "manifest_version": 3,
+    "web_accessible_resources": [
+      {
+        "resources": [ "accessible.html", "café.html" ],
+        "matches": [ "<all_urls>" ]
+      }
+    ]
+  })";
+
+  TestExtensionDir extension_dir;
+  extension_dir.WriteManifest(kManifest);
+  // Write the case-variant files first so that on case-sensitive filesystems
+  // (like Linux) the files exist to be loaded, but on case-preserving/case-
+  // insensitive filesystems (macOS, Windows) the canonical lowercase files
+  // are written second.
+  extension_dir.WriteFile(FILE_PATH_LITERAL("Accessible.html"),
+                          "accessible content");
+  extension_dir.WriteFile(FILE_PATH_LITERAL("accessible.html"),
+                          "accessible content");
+  extension_dir.WriteFile(FILE_PATH_LITERAL("CAFÉ.html"), "café content");
+  extension_dir.WriteFile(FILE_PATH_LITERAL("café.html"), "café content");
+  extension_dir.WriteFile(FILE_PATH_LITERAL("Private.html"), "private content");
+  extension_dir.WriteFile(FILE_PATH_LITERAL("private.html"), "private content");
+  const Extension* extension = LoadExtension(extension_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  GURL web_page = embedded_test_server()->GetURL("example.com", "/simple.html");
+  auto* web_contents = GetActiveWebContents();
+  ASSERT_TRUE(content::NavigateToURL(web_contents, web_page));
+
+  static constexpr char kFetchScript[] = R"(
+    window.fetchResource = async function(url) {
+      try {
+        const response = await fetch(url);
+        return await response.text();
+      } catch (e) {
+        return 'FETCH_FAILED';
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/extensions/sandboxed_pages_apitest.cc b/chrome/browser/extensions/sandboxed_pages_apitest.cc
index e5beb22..731679e4 100644
--- a/chrome/browser/extensions/sandboxed_pages_apitest.cc
+++ b/chrome/browser/extensions/sandboxed_pages_apitest.cc
@@ -556,6 +556,83 @@
       extension->id(), frame_host->GetProcess()->GetID()));
 }
 
+// Verifies that requesting a sandboxed page using case-variant path (e.g.
+// "Sandboxed.html") is still recognized as a sandboxed page.
+// Regression test for https://crbug.com/542355360.
+IN_PROC_BROWSER_TEST_F(SandboxedPagesTest, CaseInsensitiveSandboxedPagePath) {
+  static constexpr char kManifest[] =
+      R"({
+           "name": "Case-insensitive sandboxed page test",
+           "version": "0.1",
+           "manifest_version": 3,
+           "sandbox": { "pages": ["sandboxed.html", "café.html"] }
+         })";
+  static constexpr char kSandboxedHtml[] =
+      R"(<html><body>Sandboxed Page</body></html>)";
+  static constexpr char kCafeHtml[] =
+      R"(<html>
+           <head><meta charset="utf-8"></head><body>Café Page</body>
+         </html>)";
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  // Write the case-variant files first so that on case-sensitive filesystems
+  // (like Linux) the files exist to be loaded, but on case-insensitive
+  // filesystems (macOS, Windows) the canonical lowercase files are written
+  // second.
+  test_dir.WriteFile(FILE_PATH_LITERAL("Sandboxed.html"), kSandboxedHtml);
+  test_dir.WriteFile(FILE_PATH_LITERAL("sandboxed.html"), kSandboxedHtml);
+  test_dir.WriteFile(FILE_PATH_LITERAL("CAFÉ.html"), kCafeHtml);
+  test_dir.WriteFile(FILE_PATH_LITERAL("café.html"), kCafeHtml);
+
+  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  content::WebContents* web_contents = GetActiveWebContents();
+
+  // Test ASCII case mismatch.
+  {
+    // Note: Deliberately don't use Extension::GetResourceURL(), which goes
+    // through additional sanitization checks.
+    GURL uppercase_url = extension->url().Resolve("Sandboxed.html");
+    ASSERT_TRUE(NavigateToURL(web_contents, uppercase_url));
+
+    // The page should load, but should be properly sandboxed: it shouldn't
+    // have extension APIs or be hosted in a trusted process.
+    content::RenderFrameHost* frame_host = web_contents->GetPrimaryMainFrame();
+    ASSERT_TRUE(frame_host);
+    EXPECT_FALSE(frame_host->IsErrorDocument());
+    EXPECT_EQ("Sandboxed Page",
+              content::EvalJs(web_contents, "document.body.innerText"));
+    EXPECT_EQ("null", frame_host->GetLastCommittedOrigin().Serialize());
+    EXPECT_EQ("undefined",
+              content::EvalJs(web_contents, "typeof chrome.runtime"));
+    EXPECT_FALSE(ProcessMap::Get(profile())->Contains(
+        extension->id(), frame_host->GetProcess()->GetID()));
+  }
+
+  // Test non-ASCII / Unicode UTF-8 case mismatch.
+  {
+    // Note: Deliberately don't use Extension::GetResourceURL(), which goes
+    // through additional sanitization checks.
+    GURL uppercase_url = extension->url().Resolve("CAFÉ.html");
+    ASSERT_TRUE(NavigateToURL(web_contents, uppercase_url));
+
+    // The page should load, but should be properly sandboxed: it shouldn't
+    // have extension APIs or be hosted in a trusted process.
+    content::RenderFrameHost* frame_host = web_contents->GetPrimaryMainFrame();
+    ASSERT_TRUE(frame_host);
+    EXPECT_FALSE(frame_host->IsErrorDocument());
+    EXPECT_EQ("Café Page",
+              content::EvalJs(web_contents, "document.body.innerText"));
+    EXPECT_EQ("null", frame_host->GetLastCommittedOrigin().Serialize());
+    EXPECT_EQ("undefined",
+              content::EvalJs(web_contents, "typeof chrome.runtime"));
+    EXPECT_FALSE(ProcessMap::Get(profile())->Contains(
+        extension->id(), frame_host->GetProcess()->GetID()));
+  }
+}
+
 // Pages that are sandboxed with the HTML5 `sandbox` attribute are treated
 // differently from pages specified in the "sandbox" attribute in the manifest.
 // These pages *do* get extension APIs.
diff --git a/chrome/browser/extensions/web_accessible_resources_browsertest.cc b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
index f6861b432..c0df203 100644
--- a/chrome/browser/extensions/web_accessible_resources_browsertest.cc
+++ b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
@@ -129,6 +129,121 @@
   ASSERT_TRUE(content::EvalJs(web_contents, script).ExtractBool());
 }
 
+// Verifies that web accessible resource matching is case-sensitive, so
+// requests using case-variant paths to allowed resources are blocked, and
+// unlisted resources remain inaccessible.
+IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesBrowserTest,
+                       WebAccessibleResourcesAreCaseSensitive) {
+  static constexpr char kManifest[] = R"({
+    "name": "Case Sensitive WAR Test",
+    "version": "0.1",
+    "manifest_version": 3,
+    "web_accessible_resources": [
+      {
+        "resources": [ "accessible.html", "café.html" ],
+        "matches": [ "<all_urls>" ]
+      }
+    ]
+  })";
+
+  TestExtensionDir extension_dir;
+  extension_dir.WriteManifest(kManifest);
+  // Write the case-variant files first so that on case-sensitive filesystems
+  // (like Linux) the files exist to be loaded, but on case-preserving/case-
+  // insensitive filesystems (macOS, Windows) the canonical lowercase files
+  // are written second.
+  extension_dir.WriteFile(FILE_PATH_LITERAL("Accessible.html"),
+                          "accessible content");
+  extension_dir.WriteFile(FILE_PATH_LITERAL("accessible.html"),
+                          "accessible content");
+  extension_dir.WriteFile(FILE_PATH_LITERAL("CAFÉ.html"), "café content");
+  extension_dir.WriteFile(FILE_PATH_LITERAL("café.html"), "café content");
+  extension_dir.WriteFile(FILE_PATH_LITERAL("Private.html"), "private content");
+  extension_dir.WriteFile(FILE_PATH_LITERAL("private.html"), "private content");
+  const Extension* extension = LoadExtension(extension_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  GURL web_page = embedded_test_server()->GetURL("example.com", "/simple.html");
+  auto* web_contents = GetActiveWebContents();
+  ASSERT_TRUE(content::NavigateToURL(web_contents, web_page));
+
+  static constexpr char kFetchScript[] = R"(
+    window.fetchResource = async function(url) {
+      try {
+        const response = await fetch(url);
+        return await response.text();
+      } catch (e) {
+        return 'FETCH_FAILED';
+      }
+    };
+  )";
+  ASSERT_TRUE(content::ExecJs(web_contents, kFetchScript));
+
+  // Note: in this test, we deliberately don't use Extension::GetResourceURL(),
+  // which goes through additional sanitization checks.
+
+  // 1. Exact-case declared resource can be fetched.
+  GURL accessible_url = extension->url().Resolve("accessible.html");
+  EXPECT_EQ("accessible content",
+            content::EvalJs(web_contents,
+                            content::JsReplace("window.fetchResource($1)",
+                                               accessible_url)));
+
+  // 2. Case-variant declared resource is blocked because web-accessible
+  // resources are case-sensitive.
+  GURL uppercase_accessible_url = extension->url().Resolve("Accessible.html");
+  EXPECT_EQ("FETCH_FAILED",
+            content::EvalJs(web_contents,
+                            content::JsReplace("window.fetchResource($1)",
+                                               uppercase_accessible_url)));
+
+  // 3. UTF-8 declared resource cannot be fetched because URLPattern matching
+  // does not currently unescape percent-encoded paths in case-sensitive mode
+  // ("caf%C3%A9.html" vs "café.html").
+  // TODO(crbug.com/545512660): Fix percent-encoded matching for non-ASCII paths
+  // in case-sensitive mode.
+  GURL cafe_url = extension->url().Resolve("café.html");
+  EXPECT_EQ(
+      "FETCH_FAILED",
+      content::EvalJs(web_contents, content::JsReplace(
+                                        "window.fetchResource($1)", cafe_url)));
+
+  // Same as above, but using a simple construction of the extension URL.
+  // GURL::Resolve() handles the unicode escaping directly; this more closely
+  // emulates a page just requesting café.html.
+  // As above, this fails because we internally *do* still escape unicode
+  // characters (so the handling is the same).
+  // TODO(crbug.com/545512660): Fix percent-encoded matching for non-ASCII paths
+  // in case-sensitive mode.
+  std::string cafe_url_simple =
+      base::StringPrintf("%scafé.html", extension->url().spec().c_str());
+  EXPECT_EQ("FETCH_FAILED",
+            content::EvalJs(web_contents,
+                            content::JsReplace("window.fetchResource($1)",
+                                               cafe_url_simple)));
+
+  // 4. UTF-8 case-variant declared resource is also blocked.
+  GURL uppercase_cafe_url = extension->url().Resolve("CAFÉ.html");
+  EXPECT_EQ("FETCH_FAILED",
+            content::EvalJs(web_contents,
+                            content::JsReplace("window.fetchResource($1)",
+                                               uppercase_cafe_url)));
+
+  // 5. Unlisted resource (private.html) is blocked.
+  GURL private_url = extension->url().Resolve("private.html");
+  EXPECT_EQ("FETCH_FAILED",
+            content::EvalJs(
+                web_contents,
+                content::JsReplace("window.fetchResource($1)", private_url)));
+
+  // 6. Case-variant of unlisted resource (Private.html) is also blocked.
+  GURL uppercase_private_url = extension->url().Resolve("Private.html");
+  EXPECT_EQ("FETCH_FAILED",
+            content::EvalJs(web_contents,
+                            content::JsReplace("window.fetchResource($1)",
+                                               uppercase_private_url)));
+}
+
 // Exercise these resources being used in iframes in a web page. The navigation
 // flow goes through a different path than resource fetching.
 IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesBrowserTest,
diff --git a/extensions/common/extension_unittest.cc b/extensions/common/extension_unittest.cc
index 40e1dbc..c4578e6 100644
--- a/extensions/common/extension_unittest.cc
+++ b/extensions/common/extension_unittest.cc
@@ -15,9 +15,11 @@
 #include "base/test/scoped_command_line.h"
 #include "base/test/scoped_feature_list.h"
 #include "extensions/common/error_utils.h"
+#include "extensions/common/extension_builder.h"
 #include "extensions/common/extension_features.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/switches.h"
+#include "extensions/common/url_pattern_set.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using extensions::mojom::ManifestLocation;
@@ -409,4 +411,28 @@
             extension->short_name());
 }
 
+TEST(ExtensionTest, ResourceMatchesCaseSensitivity) {
+  scoped_refptr<const Extension> extension = ExtensionBuilder("test").Build();
+  ASSERT_TRUE(extension);
+
+  URLPatternSet pattern_set;
+  URLPattern pattern(URLPattern::SCHEME_EXTENSION);
+  ASSERT_EQ(URLPattern::ParseResult::kSuccess,
+            pattern.Parse(extension->url().spec() + "path.html"));
+  pattern_set.AddPattern(pattern);
+
+  // Exact match succeeds for both case_sensitive = true and false.
+  EXPECT_TRUE(extension->ResourceMatches(pattern_set, "path.html",
+                                         /*case_sensitive=*/true));
+  EXPECT_TRUE(extension->ResourceMatches(pattern_set, "path.html",
+                                         /*case_sensitive=*/false));
+
+  // Case mismatch fails when case_sensitive = true, and succeeds when
+  // case_sensitive = false.
+  EXPECT_FALSE(extension->ResourceMatches(pattern_set, "Path.html",
+                                          /*case_sensitive=*/true));
+  EXPECT_TRUE(extension->ResourceMatches(pattern_set, "Path.html",
+                                         /*case_sensitive=*/false));
+}
+
 }  // namespace extensions
diff --git a/extensions/common/manifest_handlers/csp_info_unittest.cc b/extensions/common/manifest_handlers/csp_info_unittest.cc
index 4dedf3e..10221c65 100644
--- a/extensions/common/manifest_handlers/csp_info_unittest.cc
+++ b/extensions/common/manifest_handlers/csp_info_unittest.cc
@@ -77,6 +77,10 @@
   EXPECT_EQ(kDefaultSandboxedPageCSP, CSPInfo::GetResourceContentSecurityPolicy(
                                           extension1.get(), "/t%65st"));
   EXPECT_TRUE(SandboxedPageInfo::IsSandboxedPage(extension1.get(), "/t%65st"));
+  EXPECT_EQ(kDefaultSandboxedPageCSP, CSPInfo::GetResourceContentSecurityPolicy(
+                                          extension1.get(), "/Test"));
+  EXPECT_TRUE(SandboxedPageInfo::IsSandboxedPage(extension1.get(), "/Test"));
+  EXPECT_TRUE(SandboxedPageInfo::IsSandboxedPage(extension1.get(), "/tEsT"));
   EXPECT_EQ(
       kDefaultExtensionPagesCSP,
       CSPInfo::GetResourceContentSecurityPolicy(extension1.get(), "/none"));
@@ -97,6 +101,12 @@
                                                       "/path/test%2Eext"));
   EXPECT_TRUE(
       SandboxedPageInfo::IsSandboxedPage(extension5.get(), "/path/test%2Eext"));
+  EXPECT_EQ(kDefaultSandboxedPageCSP, CSPInfo::GetResourceContentSecurityPolicy(
+                                          extension5.get(), "/PATH/TEST.EXT"));
+  EXPECT_TRUE(
+      SandboxedPageInfo::IsSandboxedPage(extension5.get(), "/PATH/TEST.EXT"));
+  EXPECT_TRUE(
+      SandboxedPageInfo::IsSandboxedPage(extension5.get(), "/path/TEST.ext"));
   EXPECT_EQ(
       kDefaultExtensionPagesCSP,
       CSPInfo::GetResourceContentSecurityPolicy(extension5.get(), "/test"));
@@ -105,6 +115,30 @@
   EXPECT_EQ(kDefaultSandboxedPageCSP, CSPInfo::GetResourceContentSecurityPolicy(
                                           extension7.get(), "/test"));
 
+  static constexpr char kManifestUtf8Sandboxed[] =
+      R"({
+           "name": "UTF-8 Sandboxed Page Test",
+           "manifest_version": 3,
+           "version": "0.1",
+           "sandbox": {
+             "pages": ["café.html"]
+           }
+         })";
... (truncated)
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.