Medium watch CVSS 7.5 chrome Authorization Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Permissions
DescriptionIncorrect authorization in Permissions
ComponentPermissions
Sub-componentWebview
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.
Tracker517432155
Fix commitd6b18fd5db5b (chromium/src) +62/-5
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-05-28 (per vendor advisory)
Fix landed2026-08-06 (public git)
Disclosed2026-09-08
Patch gap33 days — fix public in source before the advisory

Background

`<webview>` guest
An embedded, isolated content::WebContents hosted by a platform app or Controlled Frame embedder whose permission requests are meant to be routed to the embedder’s permissionrequest handler.
`OverridePermissionResult()`
A GuestViewBase/WebViewGuest hook that lets a guest short-circuit the normal content-settings permission lookup by returning a fixed content::PermissionResult.
`HostContentSettingsMap`
The profile-scoped store of per-origin content settings (grants/denials) keyed on the requesting origin, consulted by PermissionContextBase when no override forces ASK.
`PermissionStatus::ASK`
A result that forces the permission machinery into DecidePermission(), which for a webview reaches WebViewPermissionHelper::RequestGeolocationPermission() and the embedder.

Root Cause Analysis

In WebViewGuest::OverridePermissionResult(), the ASK override for geolocation was gated behind IsOwnedByControlledFrameEmbedder(): only guests owned by a Controlled Frame embedder returned PermissionStatus::ASK for blink::PermissionType::GEOLOCATION. For every other <webview> embedder (e.g. a platform-app guest), the function fell through without overriding, so the permission lookup resolved against the profile-scoped HostContentSettingsMap keyed on the guest’s own origin. A prior geolocation grant to that origin — established in a normal tab or by enterprise policy — was therefore honored silently, without ever entering GeolocationPermissionContextExtensions::DecidePermission() or reaching WebViewPermissionHelper::RequestGeolocationPermission() and the embedder’s permissionrequest handler. The invariant that an embedder always mediates its guest’s geolocation access was violated.

The fix computes permission_type up front and returns PermissionStatus::ASK for GEOLOCATION from every WebViewGuest before the Controlled-Frame-only branch, guaranteeing the embedder is always consulted; the remaining audio/video/clipboard overrides stay gated to Controlled Frame.

Key insight
The single mistake was scoping the geolocation ASK override to Controlled-Frame-owned guests only, letting all other webviews inherit ambient profile-level grants keyed on the guest origin; the fix hoists the geolocation ASK return out of the IsOwnedByControlledFrameEmbedder() branch so it applies to all guests.

Attack Path

  1. Seed a profile-level grant A user (or enterprise policy) previously granted geolocation to some origin O in a regular browser tab, recording CONTENT_SETTING_ALLOW for O in the profile’s HostContentSettingsMap.
  2. Embed that origin in a webview A platform app (a non-Controlled-Frame <webview> embedder) loads origin O inside a guest whose own origin is O.
  3. Bypass the override Because the guest is not owned by a Controlled Frame embedder, OverridePermissionResult() does not return ASK, so the geolocation lookup resolves directly against the profile grant for O.
  4. Silent grant without embedder mediation Geolocation is granted without ever calling DecidePermission() or firing the embedder’s permissionrequest event, so the embedder cannot deny or gate access as intended.

Impact Assessment

An attacker/embedder scenario gains geolocation access inside a <webview> guest that the embedder never authorized, by relying on a pre-existing profile-scoped grant (or enterprise policy) for the guest’s origin rather than the embedder’s permissionrequest decision. The advisory classifies this as incorrect authorization in Permissions; no exploited-in-the-wild status is stated. The exposure is limited to the geolocation permission for origins that already hold a profile-level grant and are embedded as webview guests; escalation beyond incorrect geolocation authorization is not demonstrated by the patch.

Changed Functions

FunctionChangeNotes
if
extensions/browser/guest_view/web_view/web_view_guest.cc
modified

Files Changed

  • chrome/browser/apps/guest_view/web_view_browsertest.cc
  • extensions/browser/guest_view/web_view/web_view_guest.cc
  • extensions/browser/guest_view/web_view/README.md

Audit Directions

  • Embedder-mediated permission overrides gated on embedder type
    Review other OverridePermissionResult() / guest permission hooks for cases where an ASK/isolation override is conditioned on IsOwnedByControlledFrameEmbedder() or similar, leaving other embedder types to fall back to profile-scoped content settings.
  • Guest-origin-keyed content settings
    Audit permission paths for <webview>/guest contexts that consult HostContentSettingsMap keyed on the guest’s own origin, since ambient profile grants can leak into embedded contexts that should require embedder approval.
  • Permission-type coverage divergence
    Check enumerations of blink::PermissionType in guest override logic (geolocation vs. audio/video/clipboard) for types that are handled inconsistently across embedder classes.
From d6b18fd5db5b042f56b73b13a1b3eaf170e68d06 Mon Sep 17 00:00:00 2001
From: Giovanni Pezzino <giovax@google.com>
Date: Thu, 06 Aug 2026 03:39:54 -0700
Subject: [PATCH] [webview] Route guest geolocation via embedder for all webviews

WebViewGuest::OverridePermissionResult() only returned ASK for
geolocation when the guest was owned by a Controlled Frame embedder. For
other <webview> embedders the permission lookup fell through to the
profile-scoped HostContentSettingsMap keyed on the guest's own origin,
so a prior grant to that origin in a regular tab (or an enterprise
policy) was applied without ever reaching DecidePermission() and the
embedder's permissionrequest handler.

Return ASK for geolocation from every WebViewGuest so that
GeolocationPermissionContextExtensions::DecidePermission() and
WebViewPermissionHelper::RequestGeolocationPermission() are always
consulted. Controlled Frame behaviour is unchanged; the remaining
Controlled-Frame-only overrides stay gated. Update the webview README
accordingly and add browser tests that seed a profile-level grant for
the guest origin and assert the embedder is still consulted.

TAG=agy

Bug: 517432155
Change-Id: I60101e39107f8650ce700529f69b1c267b5b8a89
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8193560
Commit-Queue: Giovanni Pezzino <giovax@google.com>
Auto-Submit: Giovanni Pezzino <giovax@google.com>
Reviewed-by: Simon Hangl <simonha@google.com>
Cr-Commit-Position: refs/heads/main@{#1674848}
---
diff --git a/extensions/browser/guest_view/web_view/README.md b/extensions/browser/guest_view/web_view/README.md
index e6d8e81..65ba8a2 100644
--- a/extensions/browser/guest_view/web_view/README.md
+++ b/extensions/browser/guest_view/web_view/README.md
@@ -65,7 +65,7 @@
         1.  Pointerlock is intercepted in [WebContentsImpl::RequestToLockPointer()](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/web_contents/web_contents_impl.cc;l=4503;drc=cc5be7150eef183a1b9a6716d42a396ab7c59733;bpv=0;bpt=1).
         1.  Media is intercepted in [WebContentsImpl::RequestMediaAccessPermission()](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/web_contents/web_contents_impl.cc;l=5165;drc=cc5be7150eef183a1b9a6716d42a396ab7c59733).
     1.  Geolocation - [GeolocationPermissionContextExtensions](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/geolocation/geolocation_permission_context_extensions.cc;l=51;drc=1149fe5f7bedbe8187bed8d6287a1ef19eac9b5a) which inherits [GeolocationPermissionContextDelegate](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/geolocation/geolocation_permission_context_delegate.h;l=20?q=GeolocationPermissionContextDelegate&sq=&ss=chromium%2Fchromium%2Fsrc). Intercepted in [GeolocationPermissionContextExtensions::DecidePermission()](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/geolocation/geolocation_permission_context_extensions.cc;l=71-73;drc=cc5be7150eef183a1b9a6716d42a396ab7c59733;bpv=0;bpt=1).
-        1. Geolocation also has an override where it calls [GuestViewBase::OverridePermissionResult()](https://source.chromium.org/chromium/chromium/src/+/main:components/permissions/permission_context_base.cc;l=326;drc=2046c842c9a8e7abe63a74f26e05896c15daa258) to ensure that for whatever origin, the permission look up result will always be ASK which leads into GeolocationPermissionContext::DecidePermission(). This is Controlled Frame-specific.
+        1. Geolocation also has an override where it calls [GuestViewBase::OverridePermissionResult()](https://source.chromium.org/chromium/chromium/src/+/main:components/permissions/permission_context_base.cc;l=326;drc=2046c842c9a8e7abe63a74f26e05896c15daa258) to ensure that for whatever origin, the permission look up result will always be ASK which leads into GeolocationPermissionContext::DecidePermission(), ensuring the embedder is always consulted.
     1.  HID checks whether a [RenderFrameHost](https://source.chromium.org/chromium/chromium/src/+/main:content/public/browser/render_frame_host.h;l=138?q=RenderFrameHost%20file:.h$&ss=chromium%2Fchromium%2Fsrc) is in a WebView in [ChromeHidDelegate](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/hid/chrome_hid_delegate.cc;l=192;drc=1149fe5f7bedbe8187bed8d6287a1ef19eac9b5a)
     1.  New Window is called from [WebViewGuest::CreateNewGuestWebViewWindow](https://source.chromium.org/chromium/chromium/src/+/main:extensions/browser/guest_view/web_view/web_view_guest.cc;l=685;drc=1149fe5f7bedbe8187bed8d6287a1ef19eac9b5a). Note this "newwindow" event is not from a permission request, but this is where we generate a permissionrequest event to capture it and send it to the embedder.
     1.  File System is called from [ChromeContentBrowserClient::AllowWorkerFileSystem](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/chrome_content_browser_client.cc;l=3114;drc=1149fe5f7bedbe8187bed8d6287a1ef19eac9b5a) and [ContentSettingsManagerDelegate::AllowStorageAccess](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/content_settings/content_settings_manager_delegate.cc;l=82;drc=1149fe5f7bedbe8187bed8d6287a1ef19eac9b5a)
diff --git a/extensions/browser/guest_view/web_view/web_view_guest.cc b/extensions/browser/guest_view/web_view/web_view_guest.cc
index 245fa84a..11abf755 100644
--- a/extensions/browser/guest_view/web_view/web_view_guest.cc
+++ b/extensions/browser/guest_view/web_view/web_view_guest.cc
@@ -1649,14 +1649,22 @@
     return result;
   }
 
+  blink::PermissionType permission_type;
+  if (!permissions::PermissionUtil::GetPermissionType(type, &permission_type)) {
+    return std::nullopt;
+  }
+
+  if (permission_type == blink::PermissionType::GEOLOCATION) {
+    return content::PermissionResult(
+        content::PermissionStatus::ASK,
+        content::PermissionStatusSource::UNSPECIFIED);
+  }
+
   if (IsOwnedByControlledFrameEmbedder()) {
     // Permission of content within a Controlled Frame is isolated.
     // Therefore, Controlled Frame decides what the immediate permission result
     // is.
-    const blink::PermissionType permission_type =
-        permissions::PermissionUtil::ContentSettingsTypeToPermissionType(type);
-    if (permission_type == blink::PermissionType::GEOLOCATION ||
-        permission_type == blink::PermissionType::AUDIO_CAPTURE ||
+    if (permission_type == blink::PermissionType::AUDIO_CAPTURE ||
         permission_type == blink::PermissionType::VIDEO_CAPTURE ||
         permission_type == blink::PermissionType::CLIPBOARD_READ_WRITE ||
         permission_type == blink::PermissionType::CLIPBOARD_SANITIZED_WRITE) {
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index d4aa613..bd79d6d 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -46,6 +46,7 @@
 #include "chrome/browser/autocomplete/aim_eligibility_service_factory.h"
 #include "chrome/browser/bluetooth/web_bluetooth_test_utils.h"
 #include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/devtools/devtools_window_testing.h"
 #include "chrome/browser/glic/host/glic_ui.h"
 #include "chrome/browser/glic/test_support/glic_browser_test.h"
@@ -86,6 +87,9 @@
 #include "chrome/test/base/chrome_test_utils.h"
 #include "chrome/test/base/tracing.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings_types.h"
 #include "components/contextual_tasks/public/features.h"
 #include "components/download/public/common/download_task_runner.h"
 #include "components/find_in_page/find_tab_helper.h"
@@ -780,6 +784,37 @@
     ASSERT_TRUE(done_listener.WaitUntilSatisfied());
   }
 
+  void TestHelperWithProfileGrant(const std::string& test_name,
+                                  const std::string& app_location,
+                                  ContentSettingsType permission_type,
+                                  ContentSetting setting) {
+    ASSERT_TRUE(InitializeEmbeddedTestServer());
+
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &WebViewTestBase::RedirectResponseHandler, kRedirectResponsePath,
+        embedded_test_server()->GetURL(kRedirectResponseFullPath)));
+
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &WebViewTestBase::EmptyResponseHandler, kEmptyResponsePath));
+
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &WebViewTestBase::UserAgentResponseHandler,
+        kUserAgentRedirectResponsePath,
+        embedded_test_server()->GetURL(kRedirectResponseFullPath)));
+
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &WebViewTestBase::CacheControlResponseHandler, kCacheResponsePath));
+
+    EmbeddedTestServerAcceptConnections();
+
+    GURL guest_origin = embedded_test_server()->GetURL("localhost", "/");
+    HostContentSettingsMapFactory::GetForProfile(profile())
+        ->SetContentSettingDefaultScope(guest_origin, guest_origin,
+                                        permission_type, setting);
+
+    TestHelper(test_name, app_location, NO_TEST_SERVER);
+  }
+
   // Runs media_access/allow tests.
   void MediaAccessAPIAllowTestHelper(const std::string& test_name);
 
@@ -3565,6 +3600,20 @@
              NEEDS_TEST_SERVER);
 }
 
+IN_PROC_BROWSER_TEST_P(WebViewTest,
+                       GeolocationAPIEmbedderHasNoAccessWithProfileGrant) {
+  TestHelperWithProfileGrant(
+      "testDenyDenies", "web_view/geolocation/embedder_has_no_permission",
+      ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+}
+
+IN_PROC_BROWSER_TEST_P(WebViewTest,
+                       GeolocationAPIEmbedderHasAccessDenyWithProfileGrant) {
+  TestHelperWithProfileGrant(
+      "testDeny", "web_view/geolocation/embedder_has_permission",
+      ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+}
+
 // In following GeolocationAPIEmbedderHasAccess* tests, embedder (i.e. the
 // platform app) has geolocation permission
 //
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index d4aa613..bd79d6d 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -46,6 +46,7 @@
 #include "chrome/browser/autocomplete/aim_eligibility_service_factory.h"
 #include "chrome/browser/bluetooth/web_bluetooth_test_utils.h"
 #include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/devtools/devtools_window_testing.h"
 #include "chrome/browser/glic/host/glic_ui.h"
 #include "chrome/browser/glic/test_support/glic_browser_test.h"
@@ -86,6 +87,9 @@
 #include "chrome/test/base/chrome_test_utils.h"
 #include "chrome/test/base/tracing.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings_types.h"
 #include "components/contextual_tasks/public/features.h"
 #include "components/download/public/common/download_task_runner.h"
 #include "components/find_in_page/find_tab_helper.h"
@@ -780,6 +784,37 @@
     ASSERT_TRUE(done_listener.WaitUntilSatisfied());
   }
 
+  void TestHelperWithProfileGrant(const std::string& test_name,
+                                  const std::string& app_location,
+                                  ContentSettingsType permission_type,
+                                  ContentSetting setting) {
+    ASSERT_TRUE(InitializeEmbeddedTestServer());
+
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &WebViewTestBase::RedirectResponseHandler, kRedirectResponsePath,
+        embedded_test_server()->GetURL(kRedirectResponseFullPath)));
+
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &WebViewTestBase::EmptyResponseHandler, kEmptyResponsePath));
+
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &WebViewTestBase::UserAgentResponseHandler,
+        kUserAgentRedirectResponsePath,
+        embedded_test_server()->GetURL(kRedirectResponseFullPath)));
+
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &WebViewTestBase::CacheControlResponseHandler, kCacheResponsePath));
+
+    EmbeddedTestServerAcceptConnections();
+
+    GURL guest_origin = embedded_test_server()->GetURL("localhost", "/");
+    HostContentSettingsMapFactory::GetForProfile(profile())
+        ->SetContentSettingDefaultScope(guest_origin, guest_origin,
+                                        permission_type, setting);
+
+    TestHelper(test_name, app_location, NO_TEST_SERVER);
+  }
+
   // Runs media_access/allow tests.
   void MediaAccessAPIAllowTestHelper(const std::string& test_name);
 
@@ -3565,6 +3600,20 @@
              NEEDS_TEST_SERVER);
 }
 
+IN_PROC_BROWSER_TEST_P(WebViewTest,
+                       GeolocationAPIEmbedderHasNoAccessWithProfileGrant) {
+  TestHelperWithProfileGrant(
+      "testDenyDenies", "web_view/geolocation/embedder_has_no_permission",
+      ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+}
+
+IN_PROC_BROWSER_TEST_P(WebViewTest,
+                       GeolocationAPIEmbedderHasAccessDenyWithProfileGrant) {
+  TestHelperWithProfileGrant(
+      "testDeny", "web_view/geolocation/embedder_has_permission",
+      ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+}
+
 // In following GeolocationAPIEmbedderHasAccess* tests, embedder (i.e. the
 // platform app) has geolocation permission
 //
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Geolocation manifest permission bypass in standard <webview>

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential logic flaw in permission routing allows a non-ControlledFrame <webview> to bypass the host App’s manifest permission validation and obtain precise geolocation. When a guest page requests geolocation, PermissionContextBase checks the profile-scoped HostContentSettingsMap and can immediately grant permission if the origin has a profile-wide grant, short-circuiting before manifest verification or the webview permissionrequest event.

Affected files:

  • components/permissions/permission_context_base.cc
  • extensions/browser/guest_view/web_view/web_view_guest.cc
  • chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.cc
  • chrome/browser/geolocation/geolocation_permission_context_extensions.cc

Estimated timestamp from git blame: 2024-08-27

Root Cause Analysis

In PermissionContextBase::RequestPermission (components/permissions/permission_context_base.cc), the profile-scoped permission status is retrieved first via GetPermissionStatus. If this status is GRANTED, execution short-circuits and immediately grants the permission, bypassing the DecidePermission path entirely:

content::PermissionResult result = GetPermissionStatus(*request_data, rfh);
bool status_ignorable = PermissionUtil::CanPermissionRequestIgnoreStatus(...);
if (!status_ignorable && (result.status == PermissionStatus::GRANTED || ... )) {
  ...
  NotifyPermissionSet(*request_data, std::move(callback), persist, &result, ...);
  return; // Short-circuit, DecidePermission is never reached
}

Normally, for GuestViews, OverridePermissionResult forces ASK to prevent this bypass and route the decision to the embedder, but it only does so if the embedder is owned by a ControlledFrame. For standard Chrome Apps or WebUI <webview>s, WebViewGuest::OverridePermissionResult returns std::nullopt:

std::optional<content::PermissionResult> WebViewGuest::OverridePermissionResult(
    ContentSettingsType type) const {
  auto result = web_view_permission_helper_->OverridePermissionResult(type);
  if (result) { return result; }
  if (IsOwnedByControlledFrameEmbedder()) {
    ...
    if (permission_type == blink::PermissionType::GEOLOCATION) {
      return content::PermissionResult(content::PermissionStatus::ASK, ...);
    }
  }
  return std::nullopt; // Non-ControlledFrame falls through
}

Furthermore, because IsPermissionRequestable(GEOLOCATION) returns true unconditionally, the deny gate does not trigger, and a profile-scoped lookup is performed against the HostContentSettingsMap of the profile. If the guest origin already has a profile-wide grant (e.g., from a user grant in a regular tab), GetPermissionStatus returns GRANTED and short-circuits. As a result, execution never reaches DecidePermission where the embedder’s routing is implemented. This potentially allows the guest to obtain geolocation without firing the embedder’s <webview> permissionrequest event and without verifying if the embedder possesses the geolocation manifest permission.

Potential Exploitation Scenario

An attacker could potentially perform the following steps to exploit this behavior:

  1. Package a platform app/Chrome App whose manifest includes the webview permission but explicitly lacks the geolocation manifest permission.
  2. Lure a victim user who already has a profile-wide geolocation grant for a specific trusted origin (e.g., https://example.com).
  3. Inside the app, instantiate a <webview> and navigate it to https://example.com.
  4. Execute a script inside the webview using webview.executeScript or receive a postMessage from the guest page that invokes navigator.geolocation.getCurrentPosition.
  5. The coordinates are successfully retrieved by the guest and exfiltrated to the app, bypassing the app’s lack of manifest permissions.

Note: These steps represent potential exploitation paths identified via static code analysis; our tooling does not currently have the capability to run code to produce a live proof-of-concept.

Impact

This is a confused-deputy permission escalation issue (CWE-863). An installed app lacking geolocation (or potentially audioCapture/videoCapture) manifest permissions can obtain sensitive user location or media data by loading an origin in its <webview> that already has a profile-scoped grant.

Suggested Fix

Modify WebViewGuest::OverridePermissionResult to return PermissionStatus::ASK for standard <webview>s as well for delegation-sensitive permission types like GEOLOCATION, AUDIO_CAPTURE, and VIDEO_CAPTURE. This ensures that they always route through the DecidePermission path where manifest checks and delegation logic are enforced.

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker