Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in WebView
DescriptionInsufficient validation of untrusted input in WebView
ComponentWebView
Bug ClassLogic Error
Tracker511822402
Fix commite13ac964eb38 (chromium/src) +73/-66
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc
  • android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.h
From e13ac964eb3856277e8a09a9349398aea3fcf7ea Mon Sep 17 00:00:00 2001
From: Ben Wiser <bewise@chromium.org>
Date: Thu, 11 Jun 2026 05:37:23 -0700
Subject: [PATCH] webview: Remove storage access checks for WebView cookies

WebView currently hardcodes the permission check for storage access
grants to always be denied. This would require an Android API change to
update so this isn't likely to be done soon.

We can report true for hasStorageAccess based on the 3PC status in
WebView. That remains unaffected here.

Cleaning up the dead code that pipes the storage access status to the
rest of the cookie code for WebView.

Bug: 511822402
Change-Id: I0ba38d94add7de437f87770e1664970689d55215
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7921772
Auto-Submit: Rupert Wiser <bewise@chromium.org>
Reviewed-by: Peter Beverloo <peter@chromium.org>
Commit-Queue: Rupert Wiser <bewise@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1645292}
---

diff --git a/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc
index 44996808..4b5f85e7 100644
--- a/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc
+++ b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc
@@ -106,7 +106,7 @@
     const GURL& url,
     const net::SiteForCookies& /*site_for_cookies*/,
     const url::Origin& top_frame_origin,
-    net::StorageAccessApiStatus storage_access_api_status,
+    net::StorageAccessApiStatus /*storage_access_api_status*/,
     network::mojom::CookieManagerGetOptionsPtr options,
     bool is_ad_tagged,
     bool apply_devtools_overrides,
@@ -125,10 +125,12 @@
       force_disable_third_party_cookies ||
       cookieState == PrivacySetting::kPartitionedStateAllowedOnly;
 
+  // WebView does not currently have a way to grant storage access requests with
+  // user consent so we default this to be none.
   underlying_restricted_cookie_manager_->GetAllForUrl(
-      url, site_for_cookies_, top_frame_origin, storage_access_api_status,
-      std::move(options), is_ad_tagged, apply_devtools_overrides, disable_3pcs,
-      std::move(callback));
+      url, site_for_cookies_, top_frame_origin,
+      net::StorageAccessApiStatus::kNone, std::move(options), is_ad_tagged,
+      apply_devtools_overrides, disable_3pcs, std::move(callback));
 }
 
 void AwProxyingRestrictedCookieManager::SetCanonicalCookie(
@@ -136,7 +138,7 @@
     const GURL& url,
     const net::SiteForCookies& /*site_for_cookies*/,
     const url::Origin& top_frame_origin,
-    net::StorageAccessApiStatus storage_access_api_status,
+    net::StorageAccessApiStatus /*storage_access_api_status*/,
     bool is_ad_tagged,
     bool apply_devtools_overrides,
     SetCanonicalCookieCallback callback) {
@@ -151,10 +153,12 @@
   if (cookie_params->partitioned ==
           network::mojom::RestrictedCookiePartition::PARTITIONED ||
       cookieState == PrivacySetting::kStateAllowed) {
+    // WebView does not currently have a way to grant storage access requests
+    // with user consent so we default this to be none.
     underlying_restricted_cookie_manager_->SetCanonicalCookie(
         std::move(cookie_params), url, site_for_cookies_, top_frame_origin,
-        storage_access_api_status, is_ad_tagged, apply_devtools_overrides,
-        std::move(callback));
+        net::StorageAccessApiStatus::kNone, is_ad_tagged,
+        apply_devtools_overrides, std::move(callback));
   } else {
     std::move(callback).Run(false);
   }
@@ -164,7 +168,7 @@
     const GURL& url,
     const net::SiteForCookies& /*site_for_cookies*/,
     const url::Origin& top_frame_origin,
-    net::StorageAccessApiStatus storage_access_api_status,
+    net::StorageAccessApiStatus /*storage_access_api_status*/,
     mojo::PendingRemote<network::mojom::CookieChangeListener> listener,
     AddChangeListenerCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@@ -180,16 +184,19 @@
       std::move(proxy_listener),
       proxy_listener_remote.InitWithNewPipeAndPassReceiver());
 
+  // WebView does not currently have a way to grant storage access requests with
+  // user consent so we default this to be none.
   underlying_restricted_cookie_manager_->AddChangeListener(
-      url, site_for_cookies_, top_frame_origin, storage_access_api_status,
-      std::move(proxy_listener_remote), std::move(callback));
+      url, site_for_cookies_, top_frame_origin,
+      net::StorageAccessApiStatus::kNone, std::move(proxy_listener_remote),
+      std::move(callback));
 }
 
 void AwProxyingRestrictedCookieManager::SetCookieFromString(
     const GURL& url,
     const net::SiteForCookies& /*site_for_cookies*/,
     const url::Origin& top_frame_origin,
-    net::StorageAccessApiStatus storage_access_api_status,
+    net::StorageAccessApiStatus /*storage_access_api_status*/,
     bool is_ad_tagged,
     bool apply_devtools_overrides,
     const std::string& cookie) {
@@ -209,9 +216,12 @@
   if (cookieState == PrivacySetting::kStateAllowed ||
       (parsed_cookie.IsValid() && parsed_cookie.IsPartitioned() &&
        parsed_cookie.IsSecure())) {
+    // WebView does not currently have a way to grant storage access requests
+    // with user consent so we default this to be none.
     underlying_restricted_cookie_manager_->SetCookieFromString(
-        url, site_for_cookies_, top_frame_origin, storage_access_api_status,
-        is_ad_tagged, apply_devtools_overrides, cookie);
+        url, site_for_cookies_, top_frame_origin,
+        net::StorageAccessApiStatus::kNone, is_ad_tagged,
+        apply_devtools_overrides, cookie);
   }
 }
 
@@ -219,7 +229,7 @@
     const GURL& url,
     const net::SiteForCookies& /*site_for_cookies*/,
     const url::Origin& top_frame_origin,
-    net::StorageAccessApiStatus storage_access_api_status,
+    net::StorageAccessApiStatus /*storage_access_api_status*/,
     bool get_version_shared_memory,
     bool is_ad_tagged,
     bool apply_devtools_overrides,
@@ -248,17 +258,19 @@
       base::FeatureList::IsEnabled(features::kWebViewLatchedCookiePolicy) &&
       get_version_shared_memory;
 
+  // WebView does not currently have a way to grant storage access requests with
+  // user consent so we default this to be none.
   underlying_restricted_cookie_manager_->GetCookiesString(
-      url, site_for_cookies_, top_frame_origin, storage_access_api_status,
-      use_shared_memory, is_ad_tagged, apply_devtools_overrides, disable_3pcs,
-      std::move(callback));
+      url, site_for_cookies_, top_frame_origin,
+      net::StorageAccessApiStatus::kNone, use_shared_memory, is_ad_tagged,
+      apply_devtools_overrides, disable_3pcs, std::move(callback));
 }
 
 void AwProxyingRestrictedCookieManager::CookiesEnabledFor(
     const GURL& url,
     const net::SiteForCookies& /*site_for_cookies*/,
     const url::Origin& top_frame_origin,
-    net::StorageAccessApiStatus storage_access_api_status,
+    net::StorageAccessApiStatus /*storage_access_api_status*/,
     bool apply_devtools_overrides,
     CookiesEnabledForCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
diff --git a/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.h b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.h
index e705756..8d6739e 100644
--- a/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.h
+++ b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.h
@@ -58,7 +58,7 @@
   void GetAllForUrl(const GURL& url,
                     const net::SiteForCookies& /*site_for_cookies*/,
                     const url::Origin& top_frame_origin,
-                    net::StorageAccessApiStatus storage_access_api_status,
+                    net::StorageAccessApiStatus /*storage_access_api_status*/,
                     network::mojom::CookieManagerGetOptionsPtr options,
                     bool is_ad_tagged,
                     bool apply_devtools_overrides,
@@ -69,7 +69,7 @@
       const GURL& url,
       const net::SiteForCookies& /*site_for_cookies*/,
       const url::Origin& top_frame_origin,
-      net::StorageAccessApiStatus storage_access_api_status,
+      net::StorageAccessApiStatus /*storage_access_api_status*/,
       bool is_ad_tagged,
       bool apply_devtools_overrides,
       SetCanonicalCookieCallback callback) override;
@@ -77,7 +77,7 @@
       const GURL& url,
       const net::SiteForCookies& /*site_for_cookies*/,
       const url::Origin& top_frame_origin,
-      net::StorageAccessApiStatus storage_access_api_status,
+      net::StorageAccessApiStatus /*storage_access_api_status*/,
       mojo::PendingRemote<network::mojom::CookieChangeListener> listener,
       AddChangeListenerCallback callback) override;
 
@@ -85,27 +85,29 @@
       const GURL& url,
       const net::SiteForCookies& /*site_for_cookies*/,
       const url::Origin& top_frame_origin,
-      net::StorageAccessApiStatus storage_access_api_status,
+      net::StorageAccessApiStatus /*storage_access_api_status*/,
       bool is_ad_tagged,
       bool apply_devtools_overrides,
       const std::string& cookie) override;
 
-  void GetCookiesString(const GURL& url,
-                        const net::SiteForCookies& /*site_for_cookies*/,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager_unittest.cc b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager_unittest.cc
index 48399f8..14fa8ef 100644
--- a/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager_unittest.cc
+++ b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager_unittest.cc
@@ -47,7 +47,7 @@
   void GetAllForUrl(const GURL& url,
                     const net::SiteForCookies& site_for_cookies,
                     const url::Origin& top_frame_origin,
-                    net::StorageAccessApiStatus storage_access_api_status,
+                    net::StorageAccessApiStatus /*storage_access_api_status*/,
                     network::mojom::CookieManagerGetOptionsPtr options,
                     bool is_ad_tagged,
                     bool apply_devtools_overrides,
@@ -61,7 +61,7 @@
       const GURL& url,
       const net::SiteForCookies& site_for_cookies,
       const url::Origin& top_frame_origin,
-      net::StorageAccessApiStatus storage_access_api_status,
+      net::StorageAccessApiStatus /*storage_access_api_status*/,
       bool is_ad_tagged,
       bool apply_devtools_overrides,
       SetCanonicalCookieCallback callback) override {
@@ -72,7 +72,7 @@
       const GURL& url,
       const net::SiteForCookies& site_for_cookies,
       const url::Origin& top_frame_origin,
-      net::StorageAccessApiStatus storage_access_api_status,
+      net::StorageAccessApiStatus /*storage_access_api_status*/,
       mojo::PendingRemote<network::mojom::CookieChangeListener> listener,
       AddChangeListenerCallback callback) override {
     std::move(callback).Run();
@@ -82,31 +82,33 @@
       const GURL& url,
       const net::SiteForCookies& site_for_cookies,
       const url::Origin& top_frame_origin,
-      net::StorageAccessApiStatus storage_access_api_status,
+      net::StorageAccessApiStatus /*storage_access_api_status*/,
       bool is_ad_tagged,
       bool apply_devtools_overrides,
       const std::string& cookie) override {}
 
-  void GetCookiesString(const GURL& url,
-                        const net::SiteForCookies& site_for_cookies,
-                        const url::Origin& top_frame_origin,
-                        net::StorageAccessApiStatus storage_access_api_status,
-                        bool get_version_shared_memory,
-                        bool is_ad_tagged,
-                        bool apply_devtools_overrides,
-                        bool force_disable_third_party_cookies,
-                        GetCookiesStringCallback callback) override {
+  void GetCookiesString(
+      const GURL& url,
+      const net::SiteForCookies& site_for_cookies,
+      const url::Origin& top_frame_origin,
+      net::StorageAccessApiStatus /*storage_access_api_status*/,
+      bool get_version_shared_memory,
+      bool is_ad_tagged,
+      bool apply_devtools_overrides,
+      bool force_disable_third_party_cookies,
+      GetCookiesStringCallback callback) override {
     last_get_cookies_shared_memory_param_ = get_version_shared_memory;
     std::move(callback).Run(network::mojom::kInvalidCookieVersion,
                             base::ReadOnlySharedMemoryRegion(), "");
   }
 
-  void CookiesEnabledFor(const GURL& url,
-                         const net::SiteForCookies& site_for_cookies,
-                         const url::Origin& top_frame_origin,
-                         net::StorageAccessApiStatus storage_access_api_status,
-                         bool apply_devtools_overrides,
-                         CookiesEnabledForCallback callback) override {
+  void CookiesEnabledFor(
+      const GURL& url,
+      const net::SiteForCookies& site_for_cookies,
+      const url::Origin& top_frame_origin,
+      net::StorageAccessApiStatus /*storage_access_api_status*/,
+      bool apply_devtools_overrides,
+      CookiesEnabledForCallback callback) override {
     std::move(callback).Run(true);
   }
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Third-Party Cookie Policy Bypass in WebView via Forged StorageAccessApiStatus

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 Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: In Android WebView, AwProxyingURLLoaderFactory trusts the storage_access_api_status field provided by the renderer to determine if third-party cookies should be blocked. A compromised renderer can forge this status to bypass the embedder’s setAcceptThirdPartyCookies(false) privacy policy. This represents a defense-in-depth failure due to WebView’s lack of site isolation.

Affected files:

  • android_webview/browser/network_service/aw_proxying_url_loader_factory.cc

Estimated timestamp from git blame: 2025-01-17

Summary

A potential vulnerability exists in Android WebView where AwProxyingURLLoaderFactory uses an untrusted, renderer-provided field to make privacy decisions. By forging the request.storage_access_api_status field over the URLLoaderFactory Mojo interface, a compromised renderer can suppress the flag that blocks third-party cookies. This allows the attacker to make cross-origin subresource requests with unpartitioned cookies attached, bypassing an embedder’s explicit CookieManager.getInstance().setAcceptThirdPartyCookies(webview, false) policy.

Because Android WebView currently disables Site Isolation (meaning all frames run in the same process), a compromised renderer already possesses significant capabilities to access cross-origin data. Therefore, this issue represents a defense-in-depth bypass of a privacy policy rather than a novel security boundary escape.

Technical Details

WebView leaves the underlying Network Service’s CookieSettings permissive for third-party cookies by default (in AwBrowserContext::ConfigureNetworkContextParams). To enforce third-party cookie blocking, WebView intercepts requests in the browser process via AwProxyingURLLoaderFactory::CreateLoaderAndStart and appends the network::mojom::kURLLoadOptionBlockThirdPartyCookies flag to the URLLoader options bitmask.

However, in android_webview/browser/network_service/aw_proxying_url_loader_factory.cc, the proxy checks if the request has a Storage Access API grant by directly reading a field provided by the renderer:

bool hasStorageAccess = request.storage_access_api_status ==
                        net::StorageAccessApiStatus::kAccessViaAPI;

if (!global_cookie_policy) {
  options |= network::mojom::kURLLoadOptionBlockAllCookies;
} else if (!third_party_cookie_policy && !request.url.SchemeIsFile() &&
           !hasStorageAccess) { // <--- Flaw here
  options |= network::mojom::kURLLoadOptionBlockThirdPartyCookies;
}

As documented in services/network/url_loader_util.cc, request.storage_access_api_status is not trusted for security or privacy decisions because a compromised client can spoof it.

If the renderer sets this field to kAccessViaAPI, hasStorageAccess becomes true, and the proxy fails to append the kURLLoadOptionBlockThirdPartyCookies flag. When the request reaches the Network Service, URLLoader::AllowFullCookies sees that the blocking option is missing and falls back to its permissive default, allowing unpartitioned third-party cookies to be sent.

Potential Attacker Steps

Note: These are suggested steps based on static analysis; our tooling cannot yet execute a live proof of concept.

  1. An Android application embeds a WebView and configures it to block third-party cookies (setAcceptThirdPartyCookies(false)).
  2. An attacker compromises the WebView renderer process (e.g., via a memory corruption vulnerability or UXSS).
  3. From the compromised renderer, the attacker directly constructs a network::ResourceRequest destined for https://victim.example with credentials_mode set to kInclude.
  4. The attacker manually sets request.storage_access_api_status to net::StorageAccessApiStatus::kAccessViaAPI.
  5. The attacker sends this crafted request over the URLLoaderFactory Mojo interface to the browser process.
  6. The browser process trusts the forged flag, omits the blocking option, and the Network Service attaches victim.example’s unpartitioned cookies to the request, bypassing the app’s privacy policy.

Suggested Fix

AwProxyingURLLoaderFactory must not trust request.storage_access_api_status provided by the renderer. If WebView needs to honor Storage Access API grants at this layer, it should validate the grant against a trusted browser-side authority (e.g., AwPermissionManager or the CookieManager). If WebView does not support the Storage Access API, this check should be removed entirely, and the renderer-provided value should be ignored.

Evaluated with Chrome root at commit: eca8648a4e1cdfdda68c495a6003059fed641955


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