CVE-2026-7935
Overview
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
WebContentsObserverhook fired when aWebContentscommits a new primaryPage, 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.
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
- Start speech
A page calls
speechSynthesis.speak(...), causingTtsControllerImplto begin playing aTtsUtteranceImpland setcurrent_utterance_. - Navigate away The page navigates or redirects its primary frame to another document (potentially another origin) while the utterance is still active.
- No teardown fires
Because
PrimaryPageChangedwas not overridden, none of the existing stop paths run, so the utterance keeps playing under the new page’s context. - 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
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
| Function | Change | Notes |
|---|---|---|
TEST_Fcontent/browser/speech/tts_controller_unittest.cc |
modified |
Files Changed
content/browser/speech/tts_controller_impl.cccontent/browser/speech/tts_controller_impl.hcontent/browser/speech/tts_controller_unittest.cc
Audit Directions
- Incomplete observer coverageAudit
WebContentsObserversubclasses that manage ongoing output or resources (audio, media, capture, downloads) to confirm they handlePrimaryPageChanged/navigation, not justWebContentsDestroyedand visibility. - Resource lifetime vs. document lifetimeLook 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 continuationReview 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.
Patch
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();
Regression Test / PoC
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();
Original Bug Report
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:
- Open a test HTML page containing a button that triggers
speechSynthesis.speak(). - Immediately after invoking
speechSynthesis.speak(), redirect to another page usingwindow.location.href. - 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:
-
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.
- Malicious sites could use this behavior to play audio on trusted or high-profile domains (e.g.,
-
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.
-
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.
-
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)
Is the security issue related to a crash?
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