Is Text to Speech Google Docs safe?

Medium risk

Text to Speech Google Docs sends page hostname and a snippet of the text you read aloud to Amplitude on every TTS session.

Each time you start a text-to-speech session, the extension transmits an analytics event to Amplitude that includes the site hostname, the first 32 characters of the text being read, the detected language, voice settings, and a stable anonymous user ID persisted across sessions. A secondary event also sends a normalised URL path pattern. Both identifiers — an anonymousUserId UUID and a device ID stored in chrome.storage.local — are created once and never rotated, enabling cross-session tracking.

Winston Hv2.1.0Chrome Web Store
45Risk

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

TTS Sessions Send Text Excerpts to Amplitude

Starting a text-to-speech session builds an Amplitude event with the page host, first 32 characters of the first text chunk, voice/rate settings, language/length metadata, and persistent anonymous user/device IDs.

Request fired, body empty.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You start text-to-speech on the current page.

The extension did this

The extension prepares an analytics event that includes where the reading happened and a short excerpt of the text.

02EvidenceFIELD TABLE
Fields built for the Amplitude session event
FieldValueWhy it matters
Page host
docs.google.com (illustrative)Shows which website was open when you started the reading session.
Text excerpt
Quarterly planning notes for (illustrative)Includes the first 32 characters of the first text chunk read by the extension.
Anonymous user ID
550e8400-e29b-41d4-a716-446655440000 (illustrative)Lets separate reading sessions be tied to the same browser profile over time.
Device ID
dev_1720818929123_q9x4m2ka (illustrative)Adds another stable identifier for the browser where the extension is installed.
Voice and reading settings
voice_name: Google US English, rate: 1, text_lang_detected: en, text_length: 1842 (illustrative)Describes how the reading session was configured.
Normalized URL path
/document/d/{ID}/edit (illustrative)For extraction-result analytics, this records a generalized page path pattern instead of the full path.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://api2.amplitude.com/2/httpapi
200 OK observed during dynamic analysis; recorded request and response bodies were empty.
Headers
Content-Typeapplication/json
04EvidenceCODE COMPARE
The code that does this

Session event construction and Amplitude submission

What it actually does
Readable session-start fieldsassets/player-CmA0CLvy.js
const s = (() => {
    try {
      return t.trackUrl ? new URL(t.trackUrl).host : ""
    } catch {
      return ""
    }
  })(),
  m = t.source ?? X(s),
  k = await ht(),
  N = Date.now(),
  y = k !== null && N - k < 6e4,
  M = k === null ? 0 : Math.floor((N - k) / (1e3 * 60 * 60 * 24)),
  Y = t.paragraphs.reduce((z, ct) => z + ct.text.length, 0),
  F = (((i = C[0]) == null ? void 0 : i.text) ?? "").slice(0, 32);
t.textFirstChars = F, I(v.SESSION_START, {
  [o.SOURCE]: m,
  [o.HOST]: s,
  [o.VOICE_NAME]: t.settings.voiceName ?? "",
  [o.VOICE_LANG]: t.resolvedVoiceLang,
  [o.RATE]: t.settings.rate,
  [o.TEXT_LENGTH]: Y,
  [o.TEXT_LANG_DETECTED]: t.pageLang ?? "",
  [o.VOICE_LANG_MATCHES_PAGE]: J(t.resolvedVoiceLang, t.pageLang),
  [o.TEXT_FIRST_CHARS]: F,
  [o.HOUR_OF_DAY]: new Date().getHours(),
  [o.IS_CONTINUATION]: y,
  [o.DAYS_SINCE_LAST_SESSION]: M
}), (async () => await pt({
  sessionId: await ft(),
  startedAt: t.startedAt,
  lastActivityAt: Date.now(),
  source: m,
  host: s,
  atPct: 0
}))();
Readable Amplitude POST senderassets/sentry-CSvuoUnh.js
const Ni = "https://api2.amplitude.com/2/httpapi";
async function Ai(e, t, n) {
  var a;
  const [r, s, o] = await Promise.all([yi(), pn(), Ii()]),
    i = typeof chrome < "u" && ((a = chrome.runtime) != null && a.getManifest) ? chrome.runtime.getManifest().version : "unknown";
  return {
    user_id: r,
    device_id: s,
    session_id: (n == null ? void 0 : n.sessionId) ?? o,
    time: (n == null ? void 0 : n.time) ?? Date.now(),
    event_type: e,
    app_version: i,
    platform: "Chrome Extension",
    event_properties: {
      ...t,
      product: "tts-gd"
    }
  }
}
async function ua(e, t = {}, n) {
  const r = "8f4716e33e96712ef837134a2d3f25b8";
  try {
    const s = await Ai(e, t, n),
      o = await fetch(Ni, {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          api_key: r,
          events: [s]
        }),
        keepalive: !0
      });
    o.ok || console.warn("[TTS GD] analytics track non-2xx", e, o.status)
  } catch (s) {
    console.warn("[TTS GD] analytics track failed", e, s)
  }
}
Readable persistent analytics identifiersassets/sentry-CSvuoUnh.js
const se = "anonymousUserId";
async function yi() {
  const e = await chrome.storage.local.get([se]);
  if (typeof e[se] == "string") return e[se];
  const t = crypto.randomUUID();
  return await chrome.storage.local.set({
    [se]: t
  }), t
}
const k = "ttsgd_amplitude_device_id",
  kt = "ttsgd_amplitude_session_id",
  oe = "ttsgd_unique_voices_tried",
  $e = "ttsgd_last_session_end_at",
  He = "ttsgd_install_action_tracker",
  pe = "ttsgd_hanging_session";
function Ie() {
  try {
    return typeof localStorage < "u"
  } catch {
    return !1
  }
}
function Ti() {
  const e = Math.random().toString(36).slice(2, 10);
  return `dev_${Date.now()}_${e}`
}
async function pn() {
  if (Ie()) {
    const n = localStorage.getItem(k);
    if (n) return n
  }
  const e = await chrome.storage.local.get([k]);
  if (typeof e[k] == "string") {
    const n = e[k];
    return Ie() && localStorage.setItem(k, n), n
  }
  const t = Ti();
  return await chrome.storage.local.set({
    [k]: t
  }), Ie() && localStorage.setItem(k, t), t
}
05EvidenceTHIRD PARTY LIST
External analytics destinations used by this code path
  • api2.amplitude.com

    Receives the text-to-speech session event through the /2/httpapi endpoint.

Data recipients

api2.amplitude.com
Updated 17 September 2026gfnpoanknpcndnafohkeooladnhphone