CVE-2026-113109
Overview
Files Changed
extensions/browser/api/web_request/web_request_info_unittest.ccchrome/test/data/web_apps/simple_isolated_app/guest_sw_fetch_worker.jschrome/test/data/web_apps/simple_isolated_app/guest_sw.jsextensions/browser/api/web_request/web_request_info.ccchrome/test/data/web_apps/simple_isolated_app/guest_sw_fetch_from_worker.txtchrome/browser/controlled_frame/controlled_frame_apitest.ccchrome/test/data/web_apps/simple_isolated_app/guest_sw_test.html
Original Bug Report
Bypass of <webview> traffic protection via Service Workers allows Glic WebUI compromise
Flapjack (go/flapjack), an LLM-powered static analysis tool, has identified the following potential security issue.
Overview: The is_web_view protection in the Web Request and DeclarativeNetRequest APIs can be bypassed by worker-initiated requests (e.g., Service Workers) originating from a <webview>. Because workers lack a RenderFrameHost, they are assigned a null routing ID, causing the API to skip guest process verification and incorrectly flag the traffic as standard. This allows a malicious extension to intercept the worker, inject XSS into the guest page, and potentially compromise privileged WebUIs like the Gemini side panel (chrome://glic).
Affected files:
extensions/browser/api/web_request/web_request_info.ccextensions/browser/api/declarative_net_request/ruleset_manager.ccextensions/browser/api/web_request/web_request_api.ccchrome/browser/chrome_content_browser_client.cc
Estimated timestamp from git blame: 2025-10-30
Summary
There is a potential security vulnerability in the extension network interception APIs (webRequest and declarativeNetRequest) where traffic originating from Service Workers or Shared Workers within a <webview> guest is not correctly identified as <webview> traffic.
By design, extensions are restricted from intercepting and modifying <webview> traffic unless explicitly permitted. However, because worker requests are not associated with a specific UI frame, they bypass the is_web_view check in WebRequestInfoInitParams. An attacker with a malicious extension can exploit this to intercept a guest’s Service Worker script, inject arbitrary JavaScript, and achieve persistent Cross-Site Scripting (XSS) within the <webview>. If the <webview> is hosted by a privileged WebUI (such as the Gemini side panel at chrome://glic), this XSS can be weaponized to interact with privileged Mojo bridges and exfiltrate sensitive user data.
Root Cause Analysis
The vulnerability stems from how is_web_view is initialized during the creation of a proxied URLLoaderFactory:
- When a Service Worker is registered or makes a
fetch()request within a<webview>,content::EmbeddedWorkerInstance::CreateFactoryBundleis called to create aURLLoaderFactory. - Because Service Workers are decoupled from specific UI frames,
nullptris passed as theframe_hostargument tourl_loader_factory::ContentClientParams. - This triggers
ChromeContentBrowserClient::WillCreateURLLoaderFactorywith anullptrframe. - The call is routed to
extensions::WebRequestAPI::MaybeProxyURLLoaderFactoryInternal. To determine theframe_routing_id, the code uses the ternary operator:frame ? frame->GetRoutingID() : IPC::mojom::kRoutingIdNoneSinceframeisnullptr, the routing ID becomesIPC::mojom::kRoutingIdNone(which is-2). - This
-2routing ID is passed down toWebRequestProxyingURLLoaderFactoryand subsequently toWebRequestInfoInitParams::InitializeWebViewAndFrameData. - Inside
InitializeWebViewAndFrameData(inextensions/browser/api/web_request/web_request_info.cc), the code attempts to determine if the request is from a<webview>:} else if (frame_routing_id != IPC::mojom::kRoutingIdNone) { #if BUILDFLAG(ENABLE_GUEST_VIEW) // Grab any WebView-related information if relevant. // ... (WebViewRendererState lookup occurs here) - Because
frame_routing_idis-2(kRoutingIdNone), thiselse ifbranch evaluates tofalse. TheWebViewRendererStatelookup is skipped entirely. - As a result, the
is_web_viewflag remains at its default value offalse. ThewebRequestand DNR event routers subsequently evaluate the request as standard web traffic, allowing extensions to intercept it.
Potential Exploitation Scenario
Note: As an AI agent, I cannot run or verify a functional proof-of-concept, but code analysis strongly suggests the following attack chain is possible:
- Extension Installation: An attacker publishes a malicious extension requesting
webRequestordeclarativeNetRequestpermissions for<all_urls>or*://gemini.google.com/*, which the victim installs. - Target Invocation: The user opens the Gemini side panel, which loads the highly privileged
chrome://glicWebUI. This WebUI creates a<webview>configured with a custom partition (persist:glicpart) and navigates tohttps://gemini.google.com/. - Worker Interception: The
gemini.google.comguest attempts to register or update its Service Worker (sw.js). Due to the bug, the malicious extension successfully intercepts this request. - Payload Injection: The extension modifies the HTTP response for
sw.js, injecting an attacker-controlled JavaScript payload. - Persistent XSS: The malicious Service Worker is installed. It intercepts subsequent
fetchevents from thegemini.google.comguest page and injects an XSS payload directly into the main HTML document served to the<webview>. - Privilege Escalation: The injected script executes within the
<webview>. It leverages the pre-establishedwindow.postMessagebridge (set up inchrome/browser/resources/glic/glic_api_impl/post_message_transport.ts) to communicate with the privilegedchrome://glicWebUI. - Data Theft: The attacker’s script forges a message to invoke privileged APIs exposed by
GlicApiHost, such asglicBrowserGetContextFromFocusedTab. The browser process executes this command, returning the DOM text, PDF data, or screenshot of the user’s currently focused tab, achieving a universal cross-origin read.
Proposed Fix Considerations
Fixing this issue requires identifying <webview> traffic even when a RenderFrameHost is absent.
Caution: Relying on WebViewRendererState::IsGuest(render_process_id) as a fallback may be insufficient. Service Workers for a <webview> guest are allocated their own dedicated processes (as SiteInstanceImpl::CreateForServiceWorker does not support process reuse for <webview>). Because this new process contains no frames, it is never added to WebViewRendererState’s internal maps. Therefore, WebViewRendererState will not recognize the Service Worker’s process ID as a guest.
A more robust fix should verify if the StoragePartition or SiteInstance associated with the request (which is available during factory creation) belongs to a guest, rather than relying solely on frame-based routing IDs or WebViewRendererState.
Evaluated with Chrome root at commit: 9760e6c70cd33a320713361f17c6dcca85648c0f
Results from Flapjack so far have been promising, but it can be wrong in its deductions. At this time, it does not produce proof of concepts or fuzzer tests. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve Flapjack’s accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.