Is Google Reader: Free Natural AI Text to Speech, TTS safe?

High risk

Google Reader sends captured Google TTS tokens and page or selected text to an Appspot proxy for speech synthesis.

When text-to-speech runs, the extension captures a token from Google text-to-speech requests and stores it locally for later use. It sends that token together with selected text, page text, or uploaded document text to an Appspot proxy that forwards requests to Google Text-to-Speech.

Democratic Developerv2.2.5Firefox Add-ons
72Risk
Who publishes it

Democratic Developer - no other listings under this identity

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Democratic Developer

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

Publishers can request a review.

Findings

TTS proxy receives Google token and narrated text

When you ask the extension to read text aloud, it takes a Google TTS token from gstatic and sends the narrated text to an Appspot proxy with that token in the query.

Both leave via cxl-services.appspot.com, only on TTS requests.

Severity
High unwanted
Type
Unexpected
CWE
CWE-522
Source
Dynamic sandbox
What actually happens
You did this

You ask the extension to read selected text, page text, an uploaded document, or a sample voice aloud.

The extension also exposes context-menu entries for sending the selected text or all text on the current page to Google Reader.

The extension did this

The extension posts that text to an Appspot proxy and appends a Google text-to-speech token to the request URL.

The token is collected from the gstatic text-to-speech page and stored as gr/token before the POST is made.

Fields assembled for the text-to-speech request
  • Google speech token
    token=ya29.a0AfH6SMB7F9vX6k2wQpN8r3mL1sT4uV5xY6zA7bC8dE9fG0hI1jK2lM3nO4pQ5rS6tU7vW8xY9z (illustrative)

    This request credential is added to the proxy URL for the speech request you triggered.

  • Narrated text
    Quarterly planning notes for the July 18 product launch (illustrative)

    The text you chose for narration is placed in the request body, so page, form, or document content can be included.

  • Voice and language
    voice.name=en-US-Chirp3-HD-Achernar, languageCode=en-US (from shipped defaults)

    The request also includes the voice and language settings used to generate the audio.

Captured request
POSThttps://cxl-services.appspot.com/proxy?url=https://texttospeech.googleapis.com/v1beta1/text:synthesize&token=<redacted>
The code that does this

Shipped code path that captures the token and sends narrated text

Readable version

Injected token listener

injected.js
(() => {  const originalOpen = XMLHttpRequest.prototype.open;  const originalSend = XMLHttpRequest.prototype.send;  XMLHttpRequest.prototype.open = function (method, url, async, user, password) {    this._requestUrl = url;    return originalOpen.apply(this, arguments);  };  XMLHttpRequest.prototype.send = function (body) {    const accessToken = extractTokenFromURL(this._requestUrl);    if (accessToken) {      window.dispatchEvent(new CustomEvent("AUTH_RECEIVED", {        detail: { accessToken }      }));    }    return originalSend.call(this, body);  };  function extractTokenFromURL(url) {    const match = url.match(/token=([^&]+)/);    return match ? match[1] : null;  }})();

Token storage hook

assets/index.tsx-DRtiKFi_.js
function useGoogleReaderToken() {  const [token, setToken] = useState(null);  const [isAuthenticated, setIsAuthenticated] = useState(false);  function saveToken(value) {    setToken(value);    setIsAuthenticated(Boolean(value));    localStorage.setItem("gr/token", value);  }  function onAuthReceived(event) {    const accessToken = event.detail.accessToken;    saveToken(accessToken.includes("Bearer") ? accessToken.split(" ")[1] : accessToken);  }  useEffect(() => {    window.dispatchEvent(new CustomEvent("GET_TOKEN"));    window.addEventListener("AUTH_RECEIVED", onAuthReceived);    return () => window.removeEventListener("AUTH_RECEIVED", onAuthReceived);  }, []);  return { token, isAuthenticated };}

Proxy endpoint constant

assets/preload-helper-C9jHGiCF.js
const EVENTS = {  AUTH_RECEIVED: "AUTH_RECEIVED",  GET_TOKEN: "GET_TOKEN",  CHUNK_RECEIVED: "CHUNK_RECEIVED",  CHUNK_SEND: "CHUNK_SEND"};const TTS_PROXY_URL = "https://cxl-services.appspot.com/proxy?url=https://texttospeech.googleapis.com/v1beta1/text:synthesize";

Text-to-speech POST builder

assets/index.tsx-DRtiKFi_.js
function buildSpeechBody(text, voiceName, languageCode, modelName, prompt) {  const body = {    audioConfig: {      audioEncoding: "LINEAR16",      pitch: 0,      speakingRate: 1    },    input: { text },    voice: {      languageCode,      name: voiceName,      modelName    }  };  if (prompt && prompt.trim()) {    body.input.prompt = prompt.trim();  }  return JSON.stringify(body);}async function fetchSpeechAudio(text, chunkNumber) {  let token = localStorage.getItem("gr/token");  if (!token) {    token = await waitForToken();  }  if (!token) {    showTokenError();    return;  }  const url = `${TTS_PROXY_URL}&token=${token}`;  const response = await fetch(url, {    method: "POST",    body: buildSpeechBody(text),    signal: AbortSignal.timeout(30000)  });  const json = await response.json();  return decodeAudioContent(json.audioContent);}

Context-menu text collection

assets/index.ts-DEiWyd7n.js
async function openReaderWithText(text) {  await chrome.storage.local.set({ origin: true });  await chrome.storage.local.set({ selectedText: text });  await chrome.storage.local.set({ iswebreader: Date.now() });  const tabId = await openOrFocusGstaticReader();  chrome.tabs.sendMessage(tabId, { type: "OPEN_POPUP", payload: "ORIGIN_VERIFIED" });}function getWholePageText() {  return document.body.innerText;}chrome.contextMenus.create({  id: "customContextMenu",  parentId: "googleReaderParent",  title: "Send selected text to Google Reader",  contexts: ["selection"]});chrome.contextMenus.create({  id: "customContextMenuAll",  parentId: "googleReaderParent",  title: "Send all text on current page to Google Reader",  contexts: ["page", "selection"]});
Network destinations in the TTS request path
    • cxl-services.appspot.com

    Google App Engine host that receives the tokenized POST from the extension.

    • texttospeech.googleapis.com

    Google Cloud Text-to-Speech API named in the proxy URL parameter.

What it can do

Permissions this extension asks for, as declared in version 2.2.5. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • See the address and title of every tab you have open

    tabs

  • Store data in your browser

    storage

  • Show you desktop notifications

    notifications

  • Act on the current tab, but only after you click the extension

    activeTab

  • Add items to the right-click menu

    contextMenus

  • Run its own code inside the pages you visit

    scripting

Where it sends data

Destinations our analysis observed Google Reader: Free Natural AI Text to Speech, TTS contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • texttospeech.googleapis.com

    Google Reader: Free Natural AI Text to Speech, TTS sends data to texttospeech.googleapis.com. One other extension we have analysed sends data here.

  • cxl-services.appspot.com

    Google Reader: Free Natural AI Text to Speech, TTS sends data to cxl-services.appspot.com. No other extension we have analysed sends data here.

Updated 30 September 2026amo-2926858