Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in WebXR
DescriptionInsufficient policy enforcement in WebXR
ComponentWebXR
Bug ClassLogic Error
Tracker507237563
Fix commitf52fe685df1a (chromium/src) +4/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Files Changed

  • third_party/blink/renderer/modules/xr/xr_canvas_input_provider.cc
From f52fe685df1ae0ede40d33b61958f89c5258ba3c Mon Sep 17 00:00:00 2001
From: Alexander Cooper <[email protected]>
Date: Wed, 06 May 2026 16:46:08 -0700
Subject: [PATCH] [WebXR] Ensure synthetic events don't trigger WebXR activation

Ignore untrusted pointer events in the WebXR canvas input listener to
ensure synthetic events do not trigger user activation.

Fixed: 507237563
Change-Id: I1085a28cf4aee9f9e7a94d787af91eb842e6decf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7821913
Commit-Queue: Alexander Cooper <[email protected]>
Commit-Queue: Brandon Jones <[email protected]>
Auto-Submit: Alexander Cooper <[email protected]>
Reviewed-by: Brandon Jones <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1626558}
---

diff --git a/third_party/blink/renderer/modules/xr/xr_canvas_input_provider.cc b/third_party/blink/renderer/modules/xr/xr_canvas_input_provider.cc
index 2113f5d..7f1cb43 100644
--- a/third_party/blink/renderer/modules/xr/xr_canvas_input_provider.cc
+++ b/third_party/blink/renderer/modules/xr/xr_canvas_input_provider.cc
@@ -27,6 +27,10 @@
     if (!input_provider_->ShouldProcessEvents())
       return;
 
+    if (!event->isTrusted()) {
+      return;
+    }
+
     auto* pointer_event = To<PointerEvent>(event);
     DCHECK(pointer_event);
     if (!pointer_event->isPrimary())
Loading diff…

Original Bug Report

reported by [email protected]

User Activation Bypass via synthetic PointerEvents in WebXR

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: The WebXR implementation fails to verify if a PointerEvent is trusted when processing canvas input for inline sessions. This missing check allows malicious JavaScript to dispatch synthetic events that unconditionally grant transient user activation. An attacker could potentially use this to bypass security gates for sensitive actions like opening popups or entering fullscreen without any user interaction.

Affected files:

  • third_party/blink/renderer/modules/xr/xr_canvas_input_provider.cc
  • third_party/blink/renderer/modules/xr/xr_input_source.cc

Estimated timestamp from git blame: 2020-02-19

Summary

A potential logic flaw in the WebXR module allows a malicious website to forge both renderer-side and browser-side user activation without actual user interaction. The root cause is a missing event->isTrusted() check in the input listener for WebXR inline sessions. This allows unprivileged JavaScript to trigger sensitive operations that are normally gated by a user gesture requirement.

Vulnerability Details

In third_party/blink/renderer/modules/xr/xr_canvas_input_provider.cc, the XRCanvasInputEventListener::Invoke method is registered as a standard DOM event listener on the output canvas of a non-immersive (inline) WebXR session. This handler processes PointerEvents but only verifies the isPrimary() property, which is a script-controllable member of the PointerEventInit dictionary:

void Invoke(ExecutionContext* execution_context, Event* event) override {
  if (!input_provider_->ShouldProcessEvents())
    return;

  auto* pointer_event = To<PointerEvent>(event);
  DCHECK(pointer_event);
  if (!pointer_event->isPrimary())  // JS-settable property
    return;

  // ... routes pointerup to OnPointerUp ...
}

Crucially, there is no check for event->isTrusted(). Therefore, a script-dispatched synthetic event can reach this handler.

When a synthetic pointerup event is processed, it calls input_provider_->OnPointerUp(pointer_event), which eventually triggers XRInputSource::OnSelect() in third_party/blink/renderer/modules/xr/xr_input_source.cc.

XRInputSource::OnSelect() unconditionally grants user activation by calling LocalFrame::NotifyUserActivation:

void XRInputSource::OnSelect() {
  // ...
  LocalDOMWindow* window = session_->xr()->DomWindow();
  if (!window) return;
  LocalFrame::NotifyUserActivation(
      window->GetFrame(),
      mojom::blink::UserActivationNotificationType::kInteraction);
  // ...
}

LocalFrame::NotifyUserActivation updates the renderer’s activation state and sends an IPC (UpdateUserActivationState) to the browser process. The browser process blindly trusts this IPC and updates its own user_activation_state_, bypassing the security boundary intended to ensure activation is only triggered by genuine user input.

Potential Impact

Because WebXR ‘inline’ sessions do not require a prior user gesture to initiate (if only the default ‘viewer’ feature is requested), an attacker can execute this flow entirely without user interaction.

An attacker could mint transient and sticky user activation on any HTTPS origin. This bypasses the activation gates for numerous sensitive APIs and browser features, potentially including:

  • Fullscreen: Element.requestFullscreen()
  • Popups: Bypassing the popup blocker to open new windows.
  • Clipboard: navigator.clipboard.writeText()
  • Other Gated APIs: AudioContext.resume(), navigator.vibrate(), and device-chooser prompts (e.g., USB, HID).

Suggested Steps to Reproduce

Note: These are suggested steps to trigger the vulnerability based on code analysis. Our tooling does not yet have the ability to run code to confirm a working exploit.

The following sequence could be performed by unprivileged JavaScript on an HTTPS page:

  1. Request an ‘inline’ session: const session = await navigator.xr.requestSession('inline');
  2. Create a <canvas> element and acquire a WebGL context.
  3. Update the session render state to use this canvas: session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
  4. Request an animation frame to force the engine to process the state and attach the XRCanvasInputProvider to the canvas: session.requestAnimationFrame(() => {});
  5. Once the frame starts, dispatch a synthetic event to the canvas: canvas.dispatchEvent(new PointerEvent('pointerup', {isPrimary: true}));
  6. The document now possesses user activation. The script can call APIs requiring a user gesture.

Suggested Fix

Add an explicit check to ensure the event was generated by the user agent in third_party/blink/renderer/modules/xr/xr_canvas_input_provider.cc:

void Invoke(ExecutionContext* execution_context, Event* event) override {
  if (!input_provider_->ShouldProcessEvents())
    return;

  if (!event->isTrusted())
    return;

  auto* pointer_event = To<PointerEvent>(event);
  // ...
}

Evaluated with Chrome root at commit: a1e33f5848218e21d4a16ae2c1bc94e815c30c7f


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