Medium chrome Authorization Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMissing authorization in Animation
DescriptionMissing authorization in Animation
ComponentAnimation
Sub-componentBlink>Animation
Bug ClassAuthorization Error
Tracker498075038
Fix commit9456bac0b659 (chromium/src) +8/-7
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
EventListener
third_party/blink/renderer/core/animation/event_trigger.h
modified
EventTarget
third_party/blink/renderer/core/animation/event_trigger.h
modified
EventTriggerOptions
third_party/blink/renderer/core/animation/event_trigger.h
modified
ExecutionContext
third_party/blink/renderer/core/animation/event_trigger.h
modified
Node
third_party/blink/renderer/core/animation/event_trigger.h
modified
CORE_EXPORT
third_party/blink/renderer/core/animation/event_trigger.h
modified

Files Changed

  • third_party/blink/renderer/core/animation/event_trigger_options.idl
  • third_party/blink/renderer/core/animation/event_trigger.cc
  • third_party/blink/renderer/core/animation/event_trigger.idl
  • third_party/blink/renderer/core/animation/event_trigger.h
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;
 };
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • third_party/blink/renderer/core/animation/event_trigger_options.idl
  • third_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

  1. Dictionary Unwrapping: The EventTriggerOptions dictionary expects an EventTarget. If an attacker passes a cross-origin WindowProxy (e.g., window.frames[0]), the V8 bindings convert this using NativeValueTraits<EventTarget>::NativeValue. Because Window inherits from EventTarget, the cross-origin WindowProxy passes the structural type check (HasInstance) and is successfully unwrapped into a C++ LocalDOMWindow* pointer. Crucially, dictionary unwrapping does not perform cross-origin access checks.
  2. Direct C++ Listener Registration: The unwrapped LocalDOMWindow* is passed to EventTrigger::Create and stored. When an animation is added, EventTrigger::DidAddAnimation creates a C++ NativeEventListener (TriggerEventListener) and registers it by calling event_target_->addEventListener(...) directly in C++.
  3. Bypassing Security Interceptors: This direct C++ call bypasses the V8 [CrossOrigin] interceptors that normally block JavaScript from calling window.addEventListener() on cross-origin windows. Furthermore, when the event fires, TriggerEventListener (being a native listener) does not perform the origin validation checks that a JSBasedEventListener would 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.

  1. 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).
  2. The attacker uses JavaScript to instantiate a trigger: let trigger = new EventTrigger({ eventType: 'keydown', eventTarget: window.frames[0] });
  3. The attacker creates a dummy Web Animation and attaches it: trigger.addAnimation(anim, 'play', 'none');
  4. As the user interacts with the victim iframe (e.g., typing a password), the native keydown events fire.
  5. The TriggerEventListener catches these events and updates the attacker’s animation state (e.g., changing it from ‘paused’ to ‘running’).
  6. The attacker uses requestAnimationFrame to poll the animation’s playState, 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.

View on issue tracker