CVE-2026-106365
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
EventListenerthird_party/blink/renderer/core/animation/event_trigger.h |
modified | |
EventTargetthird_party/blink/renderer/core/animation/event_trigger.h |
modified | |
EventTriggerOptionsthird_party/blink/renderer/core/animation/event_trigger.h |
modified | |
ExecutionContextthird_party/blink/renderer/core/animation/event_trigger.h |
modified | |
Nodethird_party/blink/renderer/core/animation/event_trigger.h |
modified | |
CORE_EXPORTthird_party/blink/renderer/core/animation/event_trigger.h |
modified |
Files Changed
third_party/blink/renderer/core/animation/event_trigger_options.idlthird_party/blink/renderer/core/animation/event_trigger.ccthird_party/blink/renderer/core/animation/event_trigger.idlthird_party/blink/renderer/core/animation/event_trigger.h
Patch
From 9456bac0b659b5bfc11e423626848fe5d5505e5c Mon Sep 17 00:00:00 2001 From: Stefan Zager <[email protected]> Date: Fri, 11 Sep 2026 11:55:00 -0700 Subject: [PATCH] Restrict EventTrigger target to Node. Fixed: 498075038 Change-Id: Ie652084c50eb2c27bc658a9580701cf7cccb648b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8383698 Commit-Queue: Stefan Zager <[email protected]> Reviewed-by: Philip Rogers <[email protected]> Cr-Commit-Position: refs/heads/main@{#1696486} --- diff --git a/third_party/blink/renderer/core/animation/event_trigger.cc b/third_party/blink/renderer/core/animation/event_trigger.cc index 7e124da..6d741b9 100644 --- a/third_party/blink/renderer/core/animation/event_trigger.cc +++ b/third_party/blink/renderer/core/animation/event_trigger.cc @@ -8,6 +8,7 @@ #include "third_party/blink/renderer/core/animation/animation.h" #include "third_party/blink/renderer/core/dom/events/add_event_listener_options_resolved.h" #include "third_party/blink/renderer/core/dom/events/native_event_listener.h" +#include "third_party/blink/renderer/core/dom/node.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" namespace blink { @@ -30,7 +31,7 @@ } // namespace -EventTrigger::EventTrigger(String event_type, EventTarget& event_target) +EventTrigger::EventTrigger(String event_type, Node& event_target) : event_type_(event_type), event_target_(&event_target) {} /* static */ diff --git a/third_party/blink/renderer/core/animation/event_trigger.h b/third_party/blink/renderer/core/animation/event_trigger.h index 32dd65a..f946de50 100644 --- a/third_party/blink/renderer/core/animation/event_trigger.h +++ b/third_party/blink/renderer/core/animation/event_trigger.h @@ -11,9 +11,9 @@ namespace blink { class EventListener; -class EventTarget; class EventTriggerOptions; class ExecutionContext; +class Node; class CORE_EXPORT EventTrigger : public AnimationTrigger { DEFINE_WRAPPERTYPEINFO(); @@ -23,7 +23,7 @@ EventTriggerOptions* options, ExceptionState& exception_state); - EventTrigger(String event_type, EventTarget& event_target); + EventTrigger(String event_type, Node& event_target); void Invoke(); @@ -33,7 +33,7 @@ // IDL String eventType() const { return event_type_; } - EventTarget* eventTarget() const { return event_target_.Get(); } + Node* eventTarget() const { return event_target_.Get(); } void Trace(Visitor* visitor) const override; @@ -47,7 +47,7 @@ void ClearListenerIfNecessary(); String event_type_; - WeakMember<EventTarget> event_target_; + WeakMember<Node> event_target_; WeakMember<EventListener> event_listener_; }; diff --git a/third_party/blink/renderer/core/animation/event_trigger.idl b/third_party/blink/renderer/core/animation/event_trigger.idl index ee6c968e..87cbaeb 100644 --- a/third_party/blink/renderer/core/animation/event_trigger.idl +++ b/third_party/blink/renderer/core/animation/event_trigger.idl @@ -10,5 +10,5 @@ ] interface EventTrigger : AnimationTrigger { [CallWith=ExecutionContext, RaisesException] constructor(optional EventTriggerOptions options = {}); readonly attribute DOMString eventType; - readonly attribute EventTarget eventTarget; + readonly attribute Node eventTarget; }; diff --git a/third_party/blink/renderer/core/animation/event_trigger_options.idl b/third_party/blink/renderer/core/animation/event_trigger_options.idl index 519345d..10e61e9 100644 --- a/third_party/blink/renderer/core/animation/event_trigger_options.idl +++ b/third_party/blink/renderer/core/animation/event_trigger_options.idl @@ -6,5 +6,5 @@ dictionary EventTriggerOptions { DOMString eventType; - EventTarget eventTarget; + Node eventTarget; };
Original Bug Report
SOP Bypass in EventTrigger via Cross-Origin WindowProxy
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: The experimental EventTrigger API allows attaching native event listeners to cross-origin WindowProxy objects without Same-Origin Policy (SOP) checks. This creates a side channel where an attacker can observe the precise timing of sensitive events (like keystrokes) on cross-origin same-process frames. This constitutes a potential cross-origin information leak (XS-Leak).
Affected files:
third_party/blink/renderer/core/animation/event_trigger.ccthird_party/blink/renderer/core/animation/event_trigger_options.idlthird_party/blink/renderer/core/animation/animation_trigger.cc
Estimated timestamp from git blame: 2025-10-10
Description
There is a potential Same-Origin Policy (SOP) bypass in the experimental Web Animations EventTrigger API (currently behind the --enable-experimental-web-platform-features flag). The vulnerability allows a malicious page to observe the timing of events (e.g., keydown, focus) occurring within a cross-origin iframe that shares the same renderer process.
Technical Details
- Dictionary Unwrapping: The
EventTriggerOptionsdictionary expects anEventTarget. If an attacker passes a cross-originWindowProxy(e.g.,window.frames[0]), the V8 bindings convert this usingNativeValueTraits<EventTarget>::NativeValue. BecauseWindowinherits fromEventTarget, the cross-originWindowProxypasses the structural type check (HasInstance) and is successfully unwrapped into a C++LocalDOMWindow*pointer. Crucially, dictionary unwrapping does not perform cross-origin access checks. - Direct C++ Listener Registration: The unwrapped
LocalDOMWindow*is passed toEventTrigger::Createand stored. When an animation is added,EventTrigger::DidAddAnimationcreates a C++NativeEventListener(TriggerEventListener) and registers it by callingevent_target_->addEventListener(...)directly in C++. - Bypassing Security Interceptors: This direct C++ call bypasses the V8
[CrossOrigin]interceptors that normally block JavaScript from callingwindow.addEventListener()on cross-origin windows. Furthermore, when the event fires,TriggerEventListener(being a native listener) does not perform the origin validation checks that aJSBasedEventListenerwould normally perform before invocation.
As a result, the attacker’s animation state is synchronously modified whenever the targeted event occurs in the cross-origin window.
Potential Exploitation Steps
Note: These are suggested steps based on static analysis, as our tooling cannot currently execute code to provide a working proof of concept.
- An attacker hosts a page and embeds a victim site in an
iframe(e.g., a same-site, cross-origin frame on Desktop, or a cross-origin frame on Android where strict site isolation might be relaxed). - The attacker uses JavaScript to instantiate a trigger:
let trigger = new EventTrigger({ eventType: 'keydown', eventTarget: window.frames[0] }); - The attacker creates a dummy Web Animation and attaches it:
trigger.addAnimation(anim, 'play', 'none'); - As the user interacts with the victim iframe (e.g., typing a password), the native
keydownevents fire. - The
TriggerEventListenercatches these events and updates the attacker’s animation state (e.g., changing it from ‘paused’ to ‘running’). - The attacker uses
requestAnimationFrameto poll the animation’splayState, recording the exact timing of each keystroke to create a high-resolution timing oracle (XS-Leak).
Suggested Fix
Add an explicit security check before registering the event listener or when creating the EventTrigger.
For example, in EventTrigger::Create or EventTrigger::DidAddAnimation, verify that the current execution context has permission to access the event_target_. This can be done by checking if the target is a DOMWindow and utilizing BindingSecurity::ShouldAllowAccessTo to enforce the Same-Origin Policy before allowing the trigger to be constructed or the listener to be attached.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results from 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.