Medium watch CVSS 5.4 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
5.4
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Speech
DescriptionInappropriate implementation in Speech
ComponentSpeech
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker489624550
Fix commit0793f4fdce65 (chromium/src) +20/-0
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:L
CISA KEVNot listed
CreditedQadhafy Muhammad Tera
Reported2026-03-04 (per vendor advisory)
Fix landed2026-03-31 (public git)
Disclosed2026-05-05
Patch gap35 days — fix public in source before the advisory

Background

Web Speech API (`speechSynthesis`)
a web platform feature that lets a page request text-to-speech audio, driven in the browser process by TtsControllerImpl.
`TtsUtteranceImpl`
the browser-side object representing a single queued or active speech request that produces audio output.
`WebContentsObserver`
a content-layer interface whose callbacks (e.g. WebContentsDestroyed, OnVisibilityChanged, PrimaryPageChanged) notify observers of lifecycle events on a tab’s contents.
`PrimaryPageChanged`
the WebContentsObserver hook fired when a WebContents commits a new primary Page, i.e. on a navigation or cross-origin redirect.

Root Cause Analysis

TtsControllerImpl is a WebContentsObserver that already stopped the active utterance on WebContentsDestroyed and, conditionally, on OnVisibilityChanged, but it did not implement PrimaryPageChanged.

As a result, when the owning WebContents navigated or redirected its primary page to another document (including a different origin), the previously started TtsUtterance was never canceled and its audio kept playing, so TtsController::current_utterance_ remained set and speaking across the page boundary.

The fix adds TtsControllerImpl::PrimaryPageChanged(Page&), which calls StopCurrentUtteranceAndRemoveUtterancesMatching(web_contents()) — the same teardown already used for destruction — so a page change now tears down the active and queued utterances. The added unit test confirms that NavigateAndCommit clears current_utterance_, encoding the intended invariant that audio must not outlive the page that requested it. This is a logic error (missing lifecycle hook), not a memory-safety issue, consistent with the advisory’s “Inappropriate implementation” / medium classification.

Key insight
The single mistake was an incomplete set of WebContentsObserver overrides: speech teardown was wired to destruction and visibility but not to primary-page navigation, and the fix closes that gap by canceling the utterance in a new PrimaryPageChanged override.

Attack Path

  1. Start speech A page calls speechSynthesis.speak(...), causing TtsControllerImpl to begin playing a TtsUtteranceImpl and set current_utterance_.
  2. Navigate away The page navigates or redirects its primary frame to another document (potentially another origin) while the utterance is still active.
  3. No teardown fires Because PrimaryPageChanged was not overridden, none of the existing stop paths run, so the utterance keeps playing under the new page’s context.
  4. Audio persists cross-page The result is TTS audio attributable to the prior page continuing on top of the newly loaded page, a spoofing/annoyance-grade behavior rather than a memory corruption (inferred limit of impact).

Impact Assessment

An attacker-controlled page can cause speechSynthesis audio to continue after the user has navigated away or been redirected, so audio from one document plays over another, unrelated document in the same tab. The advisory rates this medium (“Inappropriate implementation in Speech”) with no exploited-in-the-wild claim; the diff demonstrates only unwanted audio persistence, and any further escalation beyond nuisance/spoofing is not shown (inferred). Preconditions are minimal: the victim need only visit a page that starts speech and then navigates.

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

Audit Directions

  • Incomplete observer coverage
    Audit WebContentsObserver subclasses that manage ongoing output or resources (audio, media, capture, downloads) to confirm they handle PrimaryPageChanged/navigation, not just WebContentsDestroyed and visibility.
  • Resource lifetime vs. document lifetime
    Look for browser-process features that start per-page work but tie teardown only to tab destruction, allowing state to leak across same-tab navigations.
  • Cross-origin continuation
    Review features that begin on one origin and may keep running after a redirect, where attribution of the effect (sound, permission, UI) can be confused between documents.
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