Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Speech
DescriptionInappropriate implementation in Speech
ComponentSpeech
Bug ClassLogic Error
Tracker489624550
Fix commit0793f4fdce65 (chromium/src) +20/-0
CISA KEVNot listed
CreditedQadhafy Muhammad Tera
Disclosed2026-05-05

Changed Functions

FunctionChangeNotes
TEST_F
content/browser/speech/tts_controller_unittest.cc
modified

Files Changed

  • content/browser/speech/tts_controller_impl.cc
  • content/browser/speech/tts_controller_impl.h
  • content/browser/speech/tts_controller_unittest.cc
From 0793f4fdce65f5900c343dea6b2f4f33c3ef4b5d Mon Sep 17 00:00:00 2001
From: Evan Liu <[email protected]>
Date: Tue, 31 Mar 2026 12:39:40 -0700
Subject: [PATCH] Fix Web Speech API audio persisting across page navigations

Currently, speechSynthesis audio can continue playing after a page
unloads or redirects to another origin. This CL fixes the issue by
canceling the active TtsUtterance in the browser process when the
WebContents' primary page changes.

Fixed: 489624550
Change-Id: I385ffc9856c572576a27e3b95be807b66bc3f8a5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7705458
Reviewed-by: Katie D <[email protected]>
Commit-Queue: Evan Liu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1608024}
---

diff --git a/content/browser/speech/tts_controller_impl.cc b/content/browser/speech/tts_controller_impl.cc
index b15ac71f..5a345f7e6 100644
--- a/content/browser/speech/tts_controller_impl.cc
+++ b/content/browser/speech/tts_controller_impl.cc
@@ -1011,6 +1011,10 @@
   StopCurrentUtteranceAndRemoveUtterancesMatching(web_contents());
 }
 
+void TtsControllerImpl::PrimaryPageChanged(Page& page) {
+  StopCurrentUtteranceAndRemoveUtterancesMatching(web_contents());
+}
+
 void TtsControllerImpl::OnVisibilityChanged(Visibility visibility) {
   if (visibility == Visibility::HIDDEN && stop_speaking_when_hidden_)
     StopCurrentUtteranceAndRemoveUtterancesMatching(web_contents());
diff --git a/content/browser/speech/tts_controller_impl.h b/content/browser/speech/tts_controller_impl.h
index 4eb535d..ae67e2a8 100644
--- a/content/browser/speech/tts_controller_impl.h
+++ b/content/browser/speech/tts_controller_impl.h
@@ -195,6 +195,7 @@
 
   // WebContentsObserver methods
   void WebContentsDestroyed() override;
+  void PrimaryPageChanged(Page& page) override;
   void OnVisibilityChanged(Visibility visibility) override;
 
   // net::NetworkChangeNotifier::NetworkChangeObserver
diff --git a/content/browser/speech/tts_controller_unittest.cc b/content/browser/speech/tts_controller_unittest.cc
index ebacbfb..5b7d4eef 100644
--- a/content/browser/speech/tts_controller_unittest.cc
+++ b/content/browser/speech/tts_controller_unittest.cc
@@ -625,6 +625,21 @@
   EXPECT_FALSE(TtsControllerCurrentUtterance());
 }
 
+TEST_F(TtsControllerTest, StopsWhenWebContentsPrimaryPageChanged) {
+  std::unique_ptr<TestWebContents> web_contents = CreateWebContents();
+  std::unique_ptr<TtsUtteranceImpl> utterance =
+      CreateUtteranceImpl(web_contents.get());
+
+  controller()->SpeakOrEnqueue(std::move(utterance));
+  EXPECT_TRUE(controller()->IsSpeaking());
+  EXPECT_TRUE(TtsControllerCurrentUtterance());
+
+  web_contents->NavigateAndCommit(GURL("https://example.com"));
+  // Navigating to a new page should reset
+  // |TtsController::current_utterance_|.
+  EXPECT_FALSE(TtsControllerCurrentUtterance());
+}
+
 TEST_F(TtsControllerTest, StartsQueuedUtteranceWhenWebContentsDestroyed) {
   std::unique_ptr<WebContents> web_contents1 = CreateWebContents();
   std::unique_ptr<WebContents> web_contents2 = CreateWebContents();
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/speech/tts_controller_unittest.cc b/content/browser/speech/tts_controller_unittest.cc
index ebacbfb..5b7d4eef 100644
--- a/content/browser/speech/tts_controller_unittest.cc
+++ b/content/browser/speech/tts_controller_unittest.cc
@@ -625,6 +625,21 @@
   EXPECT_FALSE(TtsControllerCurrentUtterance());
 }
 
+TEST_F(TtsControllerTest, StopsWhenWebContentsPrimaryPageChanged) {
+  std::unique_ptr<TestWebContents> web_contents = CreateWebContents();
+  std::unique_ptr<TtsUtteranceImpl> utterance =
+      CreateUtteranceImpl(web_contents.get());
+
+  controller()->SpeakOrEnqueue(std::move(utterance));
+  EXPECT_TRUE(controller()->IsSpeaking());
+  EXPECT_TRUE(TtsControllerCurrentUtterance());
+
+  web_contents->NavigateAndCommit(GURL("https://example.com"));
+  // Navigating to a new page should reset
+  // |TtsController::current_utterance_|.
+  EXPECT_FALSE(TtsControllerCurrentUtterance());
+}
+
 TEST_F(TtsControllerTest, StartsQueuedUtteranceWhenWebContentsDestroyed) {
   std::unique_ptr<WebContents> web_contents1 = CreateWebContents();
   std::unique_ptr<WebContents> web_contents2 = CreateWebContents();
Loading diff…

Original Bug Report

reported by [email protected]

SpeechSynthesis audio can appear to originate from another domain after fast redirect (UI/Audio Spoofing – may trick users / tricky victim)


Report description

SpeechSynthesis audio can appear to originate from another domain after fast redirect (UI/Audio Spoofing – may trick users / tricky victim)


Bug location

Where do you want to report your vulnerability?

Chrome VRP – Report security issues affecting the Chrome browser. See program rules

Which URL (or repository) have you found the vulnerability in?

https://cnfdnfial-ops.github.io/spoof/poc.html


The problem

Please describe the technical details of the vulnerability

A vulnerability has been identified in Chromium-based browsers where audio generated via the Web Speech API (speechSynthesis) can persist after a page navigation or redirect. According to the specification, speechSynthesis is document-bound and all utterances should terminate when the page is unloaded. In this case, a PoC demonstrates that speech audio can continue to play on the target page (e.g., https://google.com) after redirecting from the originating page.


Steps to Reproduce:

  1. Open a test HTML page containing a button that triggers speechSynthesis.speak().
  2. Immediately after invoking speechSynthesis.speak(), redirect to another page using window.location.href.
  3. Observe that the speech continues playing on the target page.

PoC Example:

<button onclick="
  const utter = new SpeechSynthesisUtterance('This is a test message.');
  speechSynthesis.speak(utter);
  setTimeout(() => { window.location.href='https://google.com'; }, 1000);
">Test</button>

Expected Behavior: All speech utterances should terminate immediately when the page is unloaded or navigated away. No audio should persist on the destination page.

Actual Behavior: Speech audio continues playing on the destination page after the redirect, effectively bypassing page boundaries.


Impact / Security Implications:

  • Cross-Origin Audio Injection: Audio from one origin can play on a different origin without user consent.
  • Social Engineering / Phishing Potential: A malicious page could redirect a user to a trusted site and continue playing audio messages, potentially misleading users.
  • User Experience / Privacy Risk: Unexpected audio from unrelated origins can confuse or alarm users, violating trust expectations.

Root Cause: Chromium currently does not properly terminate active speechSynthesis utterances when a page unloads or navigates away. The audio context from the original page leaks into the new page context.

Recommended Mitigation: Ensure all active speechSynthesis utterances are canceled during page unload or navigation events (beforeunload or unload) to enforce strict page-bound behavior.


Impact analysis

Impact and Potential Impact

Immediate Impact:

  • Audio from the originating page continues to play on the destination page after navigation, violating the expected document-bound behavior of the Web Speech API (speechSynthesis).
  • Users may hear messages originating from a different site without their consent, leading to confusion or disruption of the intended user experience.

Potential Security and Privacy Implications:

  1. Cross-Origin Audio Injection:

    • Malicious sites could use this behavior to play audio on trusted or high-profile domains (e.g., google.com) without permission.
    • This represents a potential vector for cross-origin abuse of audio output.
  2. Phishing and Social Engineering:

    • Attackers could redirect users from a malicious site to a legitimate site and continue playing misleading messages.
    • Users might perceive the audio as originating from the trusted site, increasing susceptibility to phishing or deceptive instructions.
  3. User Experience and Trust Risks:

    • Unexpected or unauthorized audio on high-profile sites can erode user trust.
    • This can also cause accessibility or usability issues, especially if users rely on predictable audio feedback.
  4. Spec Compliance Issue:

    • The Web Speech API specification dictates that speech utterances must terminate when a page unloads.
    • Allowing audio to persist across navigations represents a violation of expected standards, which could affect other browsers and future web platform implementations.

Severity Consideration:

  • While not a direct code-execution vulnerability, the combination of cross-origin audio leakage and potential social engineering makes this a high-impact UX/security issue, especially for high-trust domains.

The cause

What version of Chrome have you found the security issue in?

Version 145.0.7632.160 (Official Build) (64-bit)

No, it is not related to a crash.

Choose the type of vulnerability

Mixed content

How would you like to be publicly acknowledged for your report?

Qadhafy Muhammad Tera

View on issue tracker