Low watch CVSS 6.5 chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMissing authorization in BrowserTag
DescriptionMissing authorization in BrowserTag
ComponentBrowserTag
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker513486883
Fix commitb4f89038dec6 (chromium/src) +105/-45
CWECWE-862
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-05-15 (per vendor advisory)
Fix landed2026-07-07 (public git)
Disclosed2026-08-25
Patch gap49 days — fix public in source before the advisory

Background

`DisableScreenshots` policy (`prefs::kDisableScreenshots`)
an enterprise policy, stored as a profile pref, that should block programmatic screenshots, including extension capture APIs.
`<webview>` / `<controlledframe>`
embedder-only guest-view elements (for Chrome Apps and Isolated Web Apps) whose captureVisibleRegion() method runs WebViewInternalCaptureVisibleRegionFunction in the browser process.
`WebContentsCaptureClient`
a shared base for capture functions; it asks the subclass’s GetScreenshotAccess() whether capture is allowed and maps a denial to FAILURE_REASON_SCREEN_SHOTS_DISABLED or FAILURE_REASON_SCREEN_SHOTS_DISABLED_BY_DLP.
`ExtensionsBrowserClient::IsScreenshotRestricted`
an embedder hook from the extensions/ layer to chrome/. It used to report only ChromeOS Data Leak Prevention (DLP) restrictions.

Root Cause Analysis

Each WebContentsCaptureClient subclass did its own screenshot authorization in GetScreenshotAccess(). TabsCaptureVisibleTabFunction (in chrome/) checked both prefs::kDisableScreenshots and IsScreenshotRestricted(). WebViewInternalCaptureVisibleRegionFunction lives in extensions/ and cannot read Chrome prefs directly, so it called only ExtensionsBrowserClient::Get()->IsScreenshotRestricted(); that method returned false on non-ChromeOS builds and checked only DLP on ChromeOS.

As a result, <webview>/<controlledframe> captureVisibleRegion() succeeded even when an administrator had set DisableScreenshots, a missing authorization check on a privileged capture path.

The fix moves the policy check into ChromeExtensionsBrowserClient::IsScreenshotRestricted(), which now returns base::expected<void, ScreenshotAccessError>, returns kDisabledByPreferences when kDisableScreenshots is set, and then checks DLP (ChromeOS only). Both GetScreenshotAccess() implementations now just forward to this one method, so every capture client gets the same checks.

Key insight
An enterprise-policy authorization check was duplicated in one caller (tabs.captureVisibleTab) instead of living in the shared hook, so the extensions/-layer webview capture path skipped it. The fix centralises the kDisableScreenshots and DLP checks in ChromeExtensionsBrowserClient::IsScreenshotRestricted() and has all capture clients forward to it.

Attack Path

  1. Managed profile with screenshots disabled An administrator sets DisableScreenshots, so prefs::kDisableScreenshots is true for the profile.
  2. Privileged embedder hosts a guest A Chrome App with <webview>, or an Isolated Web App with the controlled-frame permissions-policy feature (as in CaptureVisibleRegionDisabledByPolicy), embeds web content in a guest.
  3. Call `captureVisibleRegion()` The embedder calls captureVisibleRegion(), which reaches WebViewInternalCaptureVisibleRegionFunction::GetScreenshotAccess() in the browser process.
  4. Policy bypassed Before the fix the method returned kEnabled unless DLP applied, so the capture succeeded and the embedder got an image of the guest’s rendered content despite the policy.

Impact Assessment

An app that already has <webview> or <controlledframe> capability could capture screenshots of guest content on a device where the administrator has disabled screenshots, defeating an enterprise data-protection control. It needs an installed Chrome App or Isolated Web App with these embedder-only APIs (ordinary web pages cannot reach them) and a managed profile with DisableScreenshots set. The vendor advisory rates this low severity and does not report in-the-wild exploitation; NVD scores it CVSS 6.5. Using this to exfiltrate sensitive content shown in the guest is plausible but not shown by the patch (inferred).

Changed Functions

FunctionChangeNotes
if
extensions/browser/api/web_contents_capture_client.cc
modified
switch
extensions/browser/api/web_contents_capture_client.cc
modified
SkBitmap
extensions/browser/api/web_contents_capture_client.h
modified
if
chrome/browser/controlled_frame/controlled_frame_apitest.cc
modified
IN_PROC_BROWSER_TEST_F
chrome/browser/controlled_frame/controlled_frame_apitest.cc
modified

Files Changed

  • extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
  • chrome/browser/extensions/chrome_extensions_browser_client.h
  • extensions/browser/extensions_browser_client.cc
  • extensions/browser/screenshot_access.h
  • chrome/browser/extensions/chrome_extensions_browser_client.cc
  • extensions/browser/extensions_browser_client.h
  • chrome/browser/extensions/api/tabs/tabs_api.cc
  • extensions/browser/api/web_contents_capture_client.h
  • extensions/browser/api/web_contents_capture_client.cc
  • extensions/browser/BUILD.gn
  • chrome/browser/extensions/api/tabs/tabs_api.h
  • extensions/browser/api/guest_view/web_view/web_view_internal_api.h
  • chrome/browser/controlled_frame/controlled_frame_apitest.cc

Audit Directions

  • Policy checks done per caller instead of in the shared hook
    Look for other enterprise prefs (for example printing, downloads, clipboard or developer tools) that are enforced in chrome/ API functions but have parallel extensions/ or guest-view implementations that skip them.
  • Other capture surfaces
    Check that DisableScreenshots/DLP also apply to tabCapture, desktopCapture, pageCapture, DevTools Page.captureScreenshot, <controlledframe>-specific APIs and Isolated Web App capture paths.
  • Layering gaps between `extensions/` and `chrome/`
    Review other ExtensionsBrowserClient hooks whose default or Chrome implementation returns a permissive value on some platforms (#if !BUILDFLAG(...) returning false), which can quietly disable a check.
From b4f89038dec62d6ed9f362391a048aa126c419f8 Mon Sep 17 00:00:00 2001
From: Giovanni Pezzino <giovax@google.com>
Date: Tue, 07 Jul 2026 01:20:54 -0700
Subject: [PATCH] Enforce DisableScreenshots enterprise policy in webview and controlledframe

The captureVisibleRegion API used by <webview> and <controlledframe>
components failed to check the DisableScreenshots enterprise policy
preference. Because WebViewInternalCaptureVisibleRegionFunction resides
in the extensions/ layer, it only checked IsScreenshotRestricted (DLP
restrictions) and omitted global preference checks.

This CL unifies screenshot restriction checks by defining
ScreenshotAccessError in extensions/browser/screenshot_access.h and
updating ExtensionsBrowserClient::IsScreenshotRestricted() and
WebContentsCaptureClient::GetScreenshotAccess() to return
base::expected<void, ScreenshotAccessError>. In
ChromeExtensionsBrowserClient, this method checks both
prefs::kDisableScreenshots and DLP restrictions. Both
WebViewInternalCaptureVisibleRegionFunction::GetScreenshotAccess() and
TabsCaptureVisibleTabFunction::GetScreenshotAccess() now directly
forward to this unified method.

BUG=b:513486883
TAG=agy
TEST=ControlledFrameApiTest.CaptureVisibleRegionDisabledByPolicy

Change-Id: I9ef600a16bf56b78980cb54b3c246eb96a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8036583
Reviewed-by: Andrew Rayskiy <greengrape@google.com>
Reviewed-by: Devlin Cronin <rdevlin.cronin@chromium.org>
Commit-Queue: Giovanni Pezzino <giovax@google.com>
Cr-Commit-Position: refs/heads/main@{#1657764}
---
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index d09c83330..a8808f5 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -3593,20 +3593,10 @@
 TabsCaptureVisibleTabFunction::TabsCaptureVisibleTabFunction()
     : chrome_details_(this) {}
 
-WebContentsCaptureClient::ScreenshotAccess
+base::expected<void, extensions::ScreenshotAccessError>
 TabsCaptureVisibleTabFunction::GetScreenshotAccess(
     content::WebContents* web_contents) const {
-  PrefService* service =
-      Profile::FromBrowserContext(browser_context())->GetPrefs();
-  if (service->GetBoolean(prefs::kDisableScreenshots)) {
-    return ScreenshotAccess::kDisabledByPreferences;
-  }
-
-  if (ExtensionsBrowserClient::Get()->IsScreenshotRestricted(web_contents)) {
-    return ScreenshotAccess::kDisabledByDlp;
-  }
-
-  return ScreenshotAccess::kEnabled;
+  return ExtensionsBrowserClient::Get()->IsScreenshotRestricted(web_contents);
 }
 
 bool TabsCaptureVisibleTabFunction::ClientAllowsTransparency() {
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.h b/chrome/browser/extensions/api/tabs/tabs_api.h
index 065f696..a5c36f80 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.h
+++ b/chrome/browser/extensions/api/tabs/tabs_api.h
@@ -489,7 +489,7 @@
   content::WebContents* GetWebContentsForID(int window_id, std::string* error);
 
   // extensions::WebContentsCaptureClient:
-  ScreenshotAccess GetScreenshotAccess(
+  base::expected<void, extensions::ScreenshotAccessError> GetScreenshotAccess(
       content::WebContents* web_contents) const override;
   bool ClientAllowsTransparency() override;
   void OnCaptureSuccess(const SkBitmap& bitmap) override;
diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.cc b/chrome/browser/extensions/chrome_extensions_browser_client.cc
index 29c7642..459bf07 100644
--- a/chrome/browser/extensions/chrome_extensions_browser_client.cc
+++ b/chrome/browser/extensions/chrome_extensions_browser_client.cc
@@ -93,6 +93,7 @@
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/pref_names.h"
 #include "components/embedder_support/user_agent_utils.h"
+#include "components/prefs/pref_service.h"
 #include "components/privacy_sandbox/privacy_sandbox_prefs.h"
 #include "components/proxy_config/proxy_config_pref_names.h"
 #include "components/search_engines/template_url_service.h"
@@ -122,6 +123,7 @@
 #include "extensions/browser/process_manager_delegate.h"
 #include "extensions/browser/safe_browsing_delegate.h"
 #include "extensions/browser/scoped_extension_keep_alive.h"
+#include "extensions/browser/screenshot_access.h"
 #include "extensions/browser/unpacked_installer.h"
 #include "extensions/browser/url_request_util.h"
 #include "extensions/common/extension_id.h"
@@ -814,14 +816,22 @@
   return util::HasIsolatedStorage(extension_id, context);
 }
 
-bool ChromeExtensionsBrowserClient::IsScreenshotRestricted(
+base::expected<void, extensions::ScreenshotAccessError>
+ChromeExtensionsBrowserClient::IsScreenshotRestricted(
     content::WebContents* web_contents) const {
-#if !BUILDFLAG(IS_CHROMEOS)
-  return false;
-#else
-  return policy::DlpContentManager::Get()->IsScreenshotApiRestricted(
-      web_contents);
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  if (profile->GetPrefs()->GetBoolean(prefs::kDisableScreenshots)) {
+    return base::unexpected(
+        extensions::ScreenshotAccessError::kDisabledByPreferences);
+  }
+#if BUILDFLAG(IS_CHROMEOS)
+  if (policy::DlpContentManager::Get()->IsScreenshotApiRestricted(
+          web_contents)) {
+    return base::unexpected(extensions::ScreenshotAccessError::kDisabledByDlp);
+  }
 #endif
+  return base::ok();
 }
 
 bool ChromeExtensionsBrowserClient::IsValidTabId(
diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.h b/chrome/browser/extensions/chrome_extensions_browser_client.h
index 8e3cbeb..5fa368c 100644
--- a/chrome/browser/extensions/chrome_extensions_browser_client.h
+++ b/chrome/browser/extensions/chrome_extensions_browser_client.h
@@ -12,6 +12,7 @@
 #include "base/functional/callback_forward.h"
 #include "base/lazy_instance.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/types/expected.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -222,8 +223,8 @@
                            const base::FilePath& path) override;
   bool HasIsolatedStorage(const ExtensionId& extension_id,
                           content::BrowserContext* context) override;
-  bool IsScreenshotRestricted(
-      content::WebContents* web_contents) const override;
+  base::expected<void, extensions::ScreenshotAccessError>
+  IsScreenshotRestricted(content::WebContents* web_contents) const override;
   bool IsValidTabId(content::BrowserContext* context,
                     int tab_id,
                     bool include_incognito,
diff --git a/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc b/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
index 8b04468..14ff5b3 100644
--- a/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
+++ b/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
@@ -376,13 +376,10 @@
   return user_gesture();
 }
 
-WebContentsCaptureClient::ScreenshotAccess
+base::expected<void, ScreenshotAccessError>
 WebViewInternalCaptureVisibleRegionFunction::GetScreenshotAccess(
     content::WebContents* web_contents) const {
-  if (ExtensionsBrowserClient::Get()->IsScreenshotRestricted(web_contents))
-    return ScreenshotAccess::kDisabledByDlp;
-
-  return ScreenshotAccess::kEnabled;
+  return ExtensionsBrowserClient::Get()->IsScreenshotRestricted(web_contents);
 }
 
 bool WebViewInternalCaptureVisibleRegionFunction::ClientAllowsTransparency() {
diff --git a/extensions/browser/api/guest_view/web_view/web_view_internal_api.h b/extensions/browser/api/guest_view/web_view/web_view_internal_api.h
index a41a5e1..03c01ae0 100644
--- a/extensions/browser/api/guest_view/web_view/web_view_internal_api.h
+++ b/extensions/browser/api/guest_view/web_view/web_view_internal_api.h
@@ -69,7 +69,7 @@
 
  private:
   // extensions::WebContentsCaptureClient:
-  ScreenshotAccess GetScreenshotAccess(
+  base::expected<void, ScreenshotAccessError> GetScreenshotAccess(
       content::WebContents* web_contents) const override;
   bool ClientAllowsTransparency() override;
   void OnCaptureSuccess(const SkBitmap& bitmap) override;
diff --git a/extensions/browser/api/web_contents_capture_client.cc b/extensions/browser/api/web_contents_capture_client.cc
index 5f2dc0d..09847442 100644
--- a/extensions/browser/api/web_contents_capture_client.cc
+++ b/extensions/browser/api/web_contents_capture_client.cc
@@ -9,6 +9,7 @@
 #include "base/base64.h"
 #include "base/strings/strcat.h"
 #include "base/syslog_logging.h"
+#include "base/types/expected_macros.h"
 #include "build/chromeos_buildflags.h"
 #include "components/viz/common/frame_sinks/copy_output_result.h"
 #include "content/public/browser/render_widget_host.h"
@@ -39,13 +40,15 @@
   }
 
   // Check for screenshot capture restrictions.
-  ScreenshotAccess screenshot_access = GetScreenshotAccess(web_contents);
-  if (screenshot_access == ScreenshotAccess::kDisabledByPreferences) {
-    return FAILURE_REASON_SCREEN_SHOTS_DISABLED;
-  }
-  if (screenshot_access == ScreenshotAccess::kDisabledByDlp) {
-    return FAILURE_REASON_SCREEN_SHOTS_DISABLED_BY_DLP;
-  }
+  RETURN_IF_ERROR(GetScreenshotAccess(web_contents),
+                  [](ScreenshotAccessError error) {
+                    switch (error) {
+                      case ScreenshotAccessError::kDisabledByPreferences:
+                        return FAILURE_REASON_SCREEN_SHOTS_DISABLED;
+                      case ScreenshotAccessError::kDisabledByDlp:
+                        return FAILURE_REASON_SCREEN_SHOTS_DISABLED_BY_DLP;
+                    }
+                  });
 
   // The default format and quality setting used when encoding jpegs.
   const api::extension_types::ImageFormat kDefaultFormat =
diff --git a/extensions/browser/api/web_contents_capture_client.h b/extensions/browser/api/web_contents_capture_client.h
index a051442f..f69e133 100644
--- a/extensions/browser/api/web_contents_capture_client.h
+++ b/extensions/browser/api/web_contents_capture_client.h
@@ -8,7 +8,9 @@
 #include <optional>
 #include <string>
 
+#include "base/types/expected.h"
 #include "extensions/browser/extension_function.h"
+#include "extensions/browser/screenshot_access.h"
 #include "extensions/common/api/extension_types.h"
 
 class SkBitmap;
@@ -31,12 +33,7 @@
  protected:
   virtual ~WebContentsCaptureClient() {}
 
-  enum class ScreenshotAccess {
-    kEnabled,
-    kDisabledByPreferences,
-    kDisabledByDlp,
-  };
-  virtual ScreenshotAccess GetScreenshotAccess(
+  virtual base::expected<void, ScreenshotAccessError> GetScreenshotAccess(
       content::WebContents* web_contents) const = 0;
   virtual bool ClientAllowsTransparency() = 0;
 
diff --git a/extensions/browser/extensions_browser_client.cc b/extensions/browser/extensions_browser_client.cc
index 9077a1e7..0ff0338 100644
--- a/extensions/browser/extensions_browser_client.cc
+++ b/extensions/browser/extensions_browser_client.cc
@@ -161,9 +161,10 @@
   return false;
 }
 
-bool ExtensionsBrowserClient::IsScreenshotRestricted(
+base::expected<void, ScreenshotAccessError>
+ExtensionsBrowserClient::IsScreenshotRestricted(
     content::WebContents* web_contents) const {
-  return false;
+  return base::ok();
 }
 
 bool ExtensionsBrowserClient::IsValidTabId(
diff --git a/extensions/browser/extensions_browser_client.h b/extensions/browser/extensions_browser_client.h
index 082e1410..4f3c988e 100644
--- a/extensions/browser/extensions_browser_client.h
+++ b/extensions/browser/extensions_browser_client.h
@@ -13,6 +13,7 @@
 #include "base/functional/callback_forward.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/ref_counted_memory.h"
+#include "base/types/expected.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -23,6 +24,7 @@
 #include "extensions/browser/extension_event_histogram_value.h"
 #include "extensions/browser/extension_prefs_observer.h"
 #include "extensions/browser/extensions_browser_api_provider.h"
+#include "extensions/browser/screenshot_access.h"
 #include "extensions/common/extension_id.h"
 #include "extensions/common/mojom/view_type.mojom.h"
 #include "extensions/common/url_pattern_set.h"
@@ -511,9 +513,10 @@
   virtual bool HasIsolatedStorage(const ExtensionId& extension_id,
                                   content::BrowserContext* context);
 
-  // Returns whether screenshot of `web_contents` is restricted due to Data Leak
-  // Protection policy.
-  virtual bool IsScreenshotRestricted(content::WebContents* web_contents) const;
+  // Returns whether screenshot of `web_contents` is restricted due to
+  // preference or Data Leak Protection policy.
+  virtual base::expected<void, ScreenshotAccessError> IsScreenshotRestricted(
+      content::WebContents* web_contents) const;
 
   // Returns true if `tab_id` exists on `browser_context`.
   virtual bool IsValidTabId(content::BrowserContext* browser_context,
diff --git a/extensions/browser/screenshot_access.h b/extensions/browser/screenshot_access.h
new file mode 100644
index 0000000..dabf42e
--- /dev/null
+++ b/extensions/browser/screenshot_access.h
@@ -0,0 +1,17 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_SCREENSHOT_ACCESS_H_
+#define EXTENSIONS_BROWSER_SCREENSHOT_ACCESS_H_
+
+namespace extensions {
+
+enum class ScreenshotAccessError {
+  kDisabledByPreferences,
+  kDisabledByDlp,
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_SCREENSHOT_ACCESS_H_
diff --git a/chrome/browser/controlled_frame/controlled_frame_apitest.cc b/chrome/browser/controlled_frame/controlled_frame_apitest.cc
index 357088d7..3f70244 100644
--- a/chrome/browser/controlled_frame/controlled_frame_apitest.cc
+++ b/chrome/browser/controlled_frame/controlled_frame_apitest.cc
@@ -25,9 +25,11 @@
 #include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_paths.h"
+#include "chrome/common/pref_names.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/embedder_support/user_agent_utils.h"
+#include "components/prefs/pref_service.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/service_worker_context.h"
 #include "content/public/browser/service_worker_running_info.h"
@@ -479,6 +481,44 @@
   EXPECT_EQ(kEvalSuccessStr, VerifyBackgroundColorIsRed(web_view_guest));
 }
 
+IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest,
+                       CaptureVisibleRegionDisabledByPolicy) {
+  std::unique_ptr<web_app::ScopedBundledIsolatedWebApp> app =
+      web_app::IsolatedWebAppBuilder(
+          web_app::ManifestBuilder().AddPermissionsPolicy(
+              network::mojom::PermissionsPolicyFeature::kControlledFrame,
+              /*self=*/true,
+              /*origins=*/{}))
+          .BuildBundle();
+  app->TrustSigningKey();
+  ASSERT_OK_AND_ASSIGN(web_app::IsolatedWebAppUrlInfo url_info,
+                       app->Install(profile()));
+  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
+
+  ASSERT_TRUE(CreateControlledFrame(
+      app_frame, embedded_https_test_server().GetURL("/index.html")));
+
+  profile()->GetPrefs()->SetBoolean(prefs::kDisableScreenshots, true);
+
+  const std::string script = R"(
+    (async function() {
+      const frame = document.getElementsByTagName('controlledframe')[0];
+      if (!frame || !frame.request) {
+        return 'NO_FRAME';
+      }
+      try {
+        await frame.captureVisibleRegion();
+        return 'UNEXPECTED_SUCCESS';
+      } catch (err) {
+        return err.message ? err.message : String(err);
+      }
+    })();
+  )";
+
+  EXPECT_EQ("Failed to capture webview: screenshot has been disabled",
+            content::EvalJs(app_frame, script));
+}
+
 IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, EnterFullScreenControlledFrame) {
   const GURL embed_url = embedded_https_test_server().GetURL("/index.html");
   const url::Origin embed_origin = url::Origin::Create(embed_url);
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index 4c28ce46..40ecdf6 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -529,6 +529,7 @@
     "sandboxed_unpacker.h",
     "scoped_active_install.cc",
     "scoped_active_install.h",
+    "screenshot_access.h",
     "script_executor.cc",
     "script_executor.h",
     "script_injection_tracker.cc",
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/controlled_frame/controlled_frame_apitest.cc b/chrome/browser/controlled_frame/controlled_frame_apitest.cc
index 357088d7..3f70244 100644
--- a/chrome/browser/controlled_frame/controlled_frame_apitest.cc
+++ b/chrome/browser/controlled_frame/controlled_frame_apitest.cc
@@ -25,9 +25,11 @@
 #include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_paths.h"
+#include "chrome/common/pref_names.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/embedder_support/user_agent_utils.h"
+#include "components/prefs/pref_service.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/service_worker_context.h"
 #include "content/public/browser/service_worker_running_info.h"
@@ -479,6 +481,44 @@
   EXPECT_EQ(kEvalSuccessStr, VerifyBackgroundColorIsRed(web_view_guest));
 }
 
+IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest,
+                       CaptureVisibleRegionDisabledByPolicy) {
+  std::unique_ptr<web_app::ScopedBundledIsolatedWebApp> app =
+      web_app::IsolatedWebAppBuilder(
+          web_app::ManifestBuilder().AddPermissionsPolicy(
+              network::mojom::PermissionsPolicyFeature::kControlledFrame,
+              /*self=*/true,
+              /*origins=*/{}))
+          .BuildBundle();
+  app->TrustSigningKey();
+  ASSERT_OK_AND_ASSIGN(web_app::IsolatedWebAppUrlInfo url_info,
+                       app->Install(profile()));
+  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
+
+  ASSERT_TRUE(CreateControlledFrame(
+      app_frame, embedded_https_test_server().GetURL("/index.html")));
+
+  profile()->GetPrefs()->SetBoolean(prefs::kDisableScreenshots, true);
+
+  const std::string script = R"(
+    (async function() {
+      const frame = document.getElementsByTagName('controlledframe')[0];
+      if (!frame || !frame.request) {
+        return 'NO_FRAME';
+      }
+      try {
+        await frame.captureVisibleRegion();
+        return 'UNEXPECTED_SUCCESS';
+      } catch (err) {
+        return err.message ? err.message : String(err);
+      }
+    })();
+  )";
+
+  EXPECT_EQ("Failed to capture webview: screenshot has been disabled",
+            content::EvalJs(app_frame, script));
+}
+
 IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, EnterFullScreenControlledFrame) {
   const GURL embed_url = embedded_https_test_server().GetURL("/index.html");
   const url::Origin embed_origin = url::Origin::Create(embed_url);
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.