Is Speechify — Text to Speech safe?
Speechify is medium risk. Entering Speechify sidepanel voice chat requests mic access, gets a short-lived OpenAI Realtime token via Speechify, opens a WebSocket to Realtime, and sends PCM16 mic frames as input_audio_buffer.append. Testing didn't reach this feature.…
Who publishes itSpeechify Inc - no other listings under this identity, 1 shared hostname
Speechify Inc - no other listings under this identity, 1 shared hostname
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 1 hostname
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Voice chat streams microphone audio to OpenAI
Entering Speechify sidepanel voice chat requests mic access, gets a short-lived OpenAI Realtime token via Speechify, opens a WebSocket to Realtime, and sends PCM16 mic frames as input_audio_buffer.append.
Testing didn't reach this feature.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You open the Speechify sidepanel in voice-chat mode.
The sidepanel captures microphone audio, gets a short-lived OpenAI token through Speechify, and streams audio frames to OpenAI Realtime.
- Microphone audioPCM16 audio frame from a microphone stream
Your spoken voice is converted into audio frames for the realtime chat session.
- Realtime modelgpt-realtime-mini-2025-12-15
The connection names the realtime model used for the voice session.
- Ephemeral tokenopenai-insecure-api-key.redacted-session-token
A short-lived token authorizes the OpenAI WebSocket session for the voice chat.
- Page contextPage Title: Product roadmap notes; Page URL: https://docs.google.com/document/d/abc123
The sidepanel chat can include the title, URL, and visible page text from the page you are asking about.
Returns a short-lived OpenAI Realtime token used by the sidepanel WebSocket.
- Content-Type
- application/json
- Authorization
- Bearer <redacted>
- X-Speechify-Client
- DesktopExtension
WebSocket session that receives realtime audio append events.
- Sec-WebSocket-Protocol
- realtime, openai-insecure-api-key.<redacted>
Voice-mode microphone capture and OpenAI Realtime streaming
Sidepanel requests microphone audio for voice mode
sidepanel/main.jslet stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }});setStream(stream);trackEvent("Voice Mode Clicked", { flow: "realtime_voice_chat" });Background script fetches the ephemeral token
background/main.jslet body = JSON.stringify({ session: { type: "realtime", model: config.model || defaultModel, output_modalities: ["audio"], audio: { output: { voice, speed: config.speed || 1.3 } }, tools: config.tools || [], instructions: config.instructions }});let response = await fetch(`${ln.voiceTyping.url}/openai-ephemeral-token-v2`, { headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "X-Speechify-Client": "DesktopExtension" }, method: "POST", body});Sidepanel opens OpenAI Realtime with the token
sidepanel/main.jsasync connect() { let tokenResponse = await this.getOpenAIEphemeralToken(); let token = tokenResponse?.value || ""; let url = `wss://api.openai.com/v1/realtime?model=${this.config.model}`; this.ws = new WebSocket(url, ["realtime", `openai-insecure-api-key.${token}`]); this.ws.onmessage = event => { this.handleMessage(event.data); };}Sidepanel sends microphone frames as realtime audio events
sidepanel/main.jsasync sendAudio(buffer) { if (!this.isConnected || !this.ws) throw new Error("Not connected to Realtime API"); let event = { type: "input_audio_buffer.append", audio: this.arrayBufferToBase64(buffer) }; this.send(event);}- ce-voice-typing.speechify.com
Speechify endpoint that issues the short-lived OpenAI Realtime token.
- api.openai.com
OpenAI Realtime WebSocket endpoint that receives microphone audio frames.
Meeting transcripts sent for AI summaries
After Meeting Capture stops, offscreen script filters transcript lines, formats them with speaker labels, and posts them to Speechify AI summarization.
Verified fields: model, transcripts, transcriptContent, stream.
Testing stopped early.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You stop a Meeting Capture session that has final transcript lines.
The extension sends the full final transcript and speaker-labeled transcript text to Speechify AI summarization.
- AI modelgpt-4.1-mini
The request names the model the backend should use for the meeting summary.
- Transcript objects{source: "tab", speakerName: "Other Speaker", text: "Let us review the launch plan."}
Each final transcript entry can include speaker names, whether the source was tab or microphone audio, timestamps, and the spoken text.
- Full transcript text[Other Speaker]: Let us review the launch plan.
The request also includes a single combined transcript, making the whole recorded meeting available for summarization.
- Streaming flagstream: true
The endpoint is asked to stream the generated summary back as chunks.
Streaming response body parsed into meeting-summary chunks.
- Content-Type
- application/json
- Authorization
- Bearer <redacted>
- X-Speechify-Client
- DesktopExtension
Transcript formatting and AI-summary POST
Stop path forwards captured transcripts
offscreen/src/main.jsif (this.callbacks.onSummarizationRequest) { this.callbacks.onSummarizationRequest({ transcripts: this.transcripts, startTime: this.startTime, endTime: new Date().getTime() }); this.startTime = null;}TranscriptContent is built from final transcript lines
offscreen/src/main.jsoA = transcripts => { let finalRows = transcripts.filter(row => row.text.trim().length > 0 && row.isFinal); return finalRows.length === 0 ? "" : finalRows.map(row => `[${row.speakerName || (row.source === "tab" ? "Other Speaker" : "User")}]: ${row.text}`).join("\n\n");};Summarization POST includes transcript objects and combined transcript text
offscreen/src/main.jslet response = await fetch(`${ke.voiceTyping.url}/ai-scribe-summarize`, { method: "POST", body: JSON.stringify({ model: "gpt-4.1-mini", transcripts, transcriptContent, stream: true }), headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "X-Speechify-Client": "DesktopExtension" }, signal: abortController.signal});- ce-voice-typing.speechify.com
Speechify AI summarization endpoint that receives full meeting transcript content.
Meeting transcripts can be emailed through Speechify
Using Meeting Capture email sharing, the sidepanel collects recipient emails, sent to an offscreen route that posts the meeting name, summary, transcript, sender, and share link to Speechify per recipient.
Testing didn't reach this step.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You choose the meeting email-share feature and enter recipient email addresses.
The extension posts the meeting name, generated summary, full formatted transcript, sender name, and optional share link through Speechify app backend.
- Recipient emailmanager@example.com
The address you enter determines who receives the meeting email.
- Sender nameAlex Reader
The request can include the display name or email from the signed-in Speechify account.
- Meeting nameWeekly roadmap sync
The email identifies the captured meeting by its tab title.
- Generated summaryThe team reviewed launch dates and assigned follow-up work.
The generated meeting summary is included in the email request.
- Formatted transcript[{type:"left",content:"Let us review the roadmap."},{type:"right",content:"I can take the follow-up."}]
The full transcript is converted into left and right entries and included with the email request.
- Share linkhttps://app.speechify.com/item/meeting-note-123?folder=abc
If a saved library item exists, the request can include a share URL for that item.
One POST is made for each recipient; failures are collected from rejected promises.
- Content-Type
- application/json
- Authorization
- Bearer <redacted>
Email-share UI route and backend POST
Sidepanel validates recipients and calls send-email
sidepanel/chunk-34T6TALS.jsconst send = async () => { onChangeSendingEmail(true); onClose(); let result = await hc("meeting-capture/send-email", { to: recipients }); if (result?.result?.success) { setEmailRecipientsCount(recipients.length); setShowEmailSentToast(true); } onChangeSendingEmail(false);};Offscreen builds the email body and posts to Speechify app backend
offscreen/src/main.jslet failures = (await Promise.allSettled(recipients.map(to => fetch(`${ke.app.url}/api/meeting/email`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, body: JSON.stringify({ to, senderName, meetingName, summary, transcripts, ...(shareUrl ? { shareUrl } : {}) })})))).filter(result => result.status === "rejected");- app.speechify.com
Speechify app endpoint that receives meeting email-share requests.
+1 more finding not shown
What it can do
Permissions this extension asks for, as declared in version 14.8.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 14.7.0, which we have not unpacked yet.
Read and change your data on every site you visit
<all_urls> and 3 more
Run hidden pages in the background
offscreen
See the address and title of every tab you have open
tabs
Capture the video and audio of a tab
tabCapture
Add items to the right-click menu
contextMenus
Store data in your browser
storage
Schedule its own background tasks
alarms
Run its own code inside the pages you visit
scripting
Store an unlimited amount of data in your browser
unlimitedStorage
Read information about your CPU
system.cpu
Read how much memory your computer has
system.memory
Show a panel beside the page
sidePanel