Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactPolicy bypass in Permissions
DescriptionPolicy bypass in Permissions
ComponentPermissions
Bug ClassLogic Error
Tracker499257860
Fix commita0d89547f245 (chromium/src) +10/-8
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Files Changed

  • components/permissions/permission_util.cc
  • content/browser/permissions/permission_util.cc
From a0d89547f245e097100233e36011b94431b3c075 Mon Sep 17 00:00:00 2001
From: Antonio Sartori <[email protected]>
Date: Tue, 28 Apr 2026 03:04:57 -0700
Subject: [PATCH] [permissions] Fall back to visible URL only for main frame origins

Permissions for top-level sandboxed origins (i.e. network URLs served with CSP: sandbox) use the actual URLs origin. However, for non-top-level opaque origins, it does not make sense to fall-back to the visible URL origin).

Bug: 499257860
Fixed: 499257860
Change-Id: If77a4cdbbe8096d6968d77669f121aaf6eed7b20
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7791424
Reviewed-by: Andy Paicu <[email protected]>
Commit-Queue: Antonio Sartori <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1621636}
---

diff --git a/components/permissions/permission_util.cc b/components/permissions/permission_util.cc
index 33019cb5..7ed353d3 100644
--- a/components/permissions/permission_util.cc
+++ b/components/permissions/permission_util.cc
@@ -452,7 +452,7 @@
 // content/browser/permissions/permission_util.cc.
 GURL PermissionUtil::GetLastCommittedOriginAsURL(
     content::RenderFrameHost* render_frame_host) {
-  DCHECK(render_frame_host);
+  CHECK(render_frame_host);
 
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(render_frame_host);
@@ -471,12 +471,13 @@
   }
 #endif
 
-  if (render_frame_host->GetLastCommittedOrigin().GetURL().is_empty()) {
+  GURL origin = render_frame_host->GetLastCommittedOrigin().GetURL();
+  if (origin.is_empty() && render_frame_host->IsInPrimaryMainFrame()) {
     if (!web_contents->GetVisibleURL().is_empty()) {
-      return web_contents->GetVisibleURL();
+      origin = web_contents->GetVisibleURL();
     }
   }
-  return render_frame_host->GetLastCommittedOrigin().GetURL();
+  return origin;
 }
 
 ContentSettingsType PermissionUtil::PermissionTypeToContentSettingsTypeSafe(
diff --git a/content/browser/permissions/permission_util.cc b/content/browser/permissions/permission_util.cc
index 1246c68..c03cac1 100644
--- a/content/browser/permissions/permission_util.cc
+++ b/content/browser/permissions/permission_util.cc
@@ -69,7 +69,7 @@
 // components/permissions/permission_util.cc.
 GURL PermissionUtil::GetLastCommittedOriginAsURL(
     content::RenderFrameHost* render_frame_host) {
-  DCHECK(render_frame_host);
+  CHECK(render_frame_host);
 
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(render_frame_host);
@@ -88,12 +88,13 @@
   }
 #endif
 
-  if (render_frame_host->GetLastCommittedOrigin().GetURL().is_empty()) {
+  GURL origin = render_frame_host->GetLastCommittedOrigin().GetURL();
+  if (origin.is_empty() && render_frame_host->IsInPrimaryMainFrame()) {
     if (!web_contents->GetVisibleURL().is_empty()) {
-      return web_contents->GetVisibleURL();
+      origin = web_contents->GetVisibleURL();
     }
   }
-  return render_frame_host->GetLastCommittedOrigin().GetURL();
+  return origin;
 }
 
 bool PermissionUtil::IsDomainOverride(
Loading diff…

Original Bug Report

reported by [email protected]

CSP sandbox bypass in permission requests via GetVisibleURL fallback

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 without the security team.

Overview: A potential logic flaw in PermissionUtil::GetLastCommittedOriginAsURL causes it to incorrectly fall back to the visible URL when a top-level document has an opaque origin. This allows sandboxed documents to request delegated permissions (like Geolocation) using the hosting origin’s identity. An attacker could exploit this to bypass CSP sandbox isolation, leading to silent grant reuse or permission prompt spoofing.

Affected files:

  • components/permissions/permission_util.cc
  • content/browser/permissions/permission_util.cc
  • components/permissions/permission_manager.cc
  • components/permissions/permission_context_base.cc
  • content/browser/permissions/permission_controller_impl.cc
  • components/permissions/permission_request_manager.cc

Estimated timestamp from git blame: 2024-08-13

Background

The Content-Security-Policy (CSP) sandbox directive is designed to isolate untrusted content by assigning it an opaque origin, which prevents it from inheriting the authority of its hosting origin. However, a potential logic flaw allows a sandboxed top-level document to bypass this restriction for certain delegated permissions.

Vulnerability Details

Initial logic and parameters are validated: When a top-level document is served with Content-Security-Policy: sandbox allow-scripts (without allow-same-origin), it commits with an opaque origin. If the document requests a delegated permission such as Geolocation via navigator.geolocation.getCurrentPosition(), standard Permissions Policy and secure context checks in Blink succeed, as the opaque origin is same-origin with itself and inherits the precursor’s trustworthy status. The request is subsequently routed to the browser process’s PermissionManager::RequestPermissionsInternal.

At this point, the requesting_origin is correctly identified as an empty URL due to the opaque origin.

However, the logic leaps directly to a fallback mechanism in PermissionUtil::GetLastCommittedOriginAsURL. Because the origin’s URL is empty, the function falls back to web_contents->GetVisibleURL():

if (render_frame_host->GetLastCommittedOrigin().GetURL().is_empty()) {
  if (!web_contents->GetVisibleURL().is_empty()) {
    return web_contents->GetVisibleURL();
  }
}

Because Geolocation uses PermissionDelegationMode::kDelegated, PermissionUtil::GetCanonicalOrigin ignores the empty requesting origin and adopts this visible URL as the embedding origin. Consequently, the request is validated against the hosting origin’s visible URL rather than being rejected for having an invalid/opaque origin, completely bypassing the sandbox isolation.

Potential Attack Steps

Note: These are suggested steps based on static analysis; our tooling does not currently run active proof-of-concept exploits.

  1. An attacker places user-generated content on a victim site (e.g., https://victim.com/ugc-viewer).
  2. The endpoint serves the content with the response header Content-Security-Policy: sandbox allow-scripts.
  3. The attacker’s HTML executes a permission request, such as navigator.geolocation.getCurrentPosition(...).
  4. Impact 1 (Silent Grant Reuse): If the victim previously granted location access to victim.com, the sandboxed document silently inherits the permission and receives location data without prompting the user.
  5. Impact 2 (Prompt Spoofing): If no grant exists, the browser prompts the user, identifying the requester as victim.com instead of an untrusted or null origin, deceiving them into granting access.

Suggested Fix

Modify PermissionUtil::GetLastCommittedOriginAsURL so that it does not fall back to GetVisibleURL for properly committed opaque origins. The fallback should be restricted to specific edge cases (e.g., pending navigations or specific Android file:/// schemes). Ensure that permission canonicalization correctly rejects or maintains the empty URL state for opaque origins to prevent unauthorized delegation.

Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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
Links in the report