Is Speechify — Text to Speech safe?

Medium risk

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.…

Speechifyv14.7.0Chrome Web Store
45Risk
Who publishes it

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.

Publisher
Speechify
Declared legal entity
Speechify Inc
Registered address
7901 4th St N, STE 6193, St. Petersburg, FL 33702, US
Registered contact
Speechify

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.

openevidence.com
Also called by 4 other listings, including Kortex

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

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.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open the Speechify sidepanel in voice-chat mode.

The extension did this

The sidepanel captures microphone audio, gets a short-lived OpenAI token through Speechify, and streams audio frames to OpenAI Realtime.

02EvidenceFIELD TABLE
Data used by the realtime voice-chat path
FieldValueWhy it matters
Microphone audio
PCM16 audio frame from a microphone streamYour spoken voice is converted into audio frames for the realtime chat session.
Realtime model
gpt-realtime-mini-2025-12-15The connection names the realtime model used for the voice session.
Ephemeral token
openai-insecure-api-key.redacted-session-tokenA short-lived token authorizes the OpenAI WebSocket session for the voice chat.
Page context
Page Title: Product roadmap notes; Page URL: https://docs.google.com/document/d/abc123The sidepanel chat can include the title, URL, and visible page text from the page you are asking about.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://ce-voice-typing.speechify.com/openai-ephemeral-token-v2
Returns a short-lived OpenAI Realtime token used by the sidepanel WebSocket.
Headers
Content-Typeapplication/json
AuthorizationBearer <redacted>
X-Speechify-ClientDesktopExtension
04EvidenceNETWORK CAPTURE
Captured request
GETwss://api.openai.com/v1/realtime?model=gpt-realtime-mini-2025-12-15
WebSocket session that receives realtime audio append events.
Headers
Sec-WebSocket-Protocolrealtime, openai-insecure-api-key.<redacted>
05EvidenceCODE COMPARE
The code that does this

Voice-mode microphone capture and OpenAI Realtime streaming

What it actually does
Sidepanel requests microphone audio for voice modesidepanel/main.js
let 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 tokenbackground/main.js
let 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 tokensidepanel/main.js
async 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 eventssidepanel/main.js
async 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);
}
06EvidenceTHIRD PARTY LIST
Remote services used by voice chat
  • 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.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

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.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You stop a Meeting Capture session that has final transcript lines.

The extension did this

The extension sends the full final transcript and speaker-labeled transcript text to Speechify AI summarization.

02EvidenceFIELD TABLE
Fields sent to the summarization endpoint
FieldValueWhy it matters
AI model
gpt-4.1-miniThe 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 flag
stream: trueThe endpoint is asked to stream the generated summary back as chunks.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://ce-voice-typing.speechify.com/ai-scribe-summarize
Streaming response body parsed into meeting-summary chunks.
Headers
Content-Typeapplication/json
AuthorizationBearer <redacted>
X-Speechify-ClientDesktopExtension
04EvidenceCODE COMPARE
The code that does this

Transcript formatting and AI-summary POST

What it actually does
Stop path forwards captured transcriptsoffscreen/src/main.js
if (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 linesoffscreen/src/main.js
oA = 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 textoffscreen/src/main.js
let 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
});
05EvidenceTHIRD PARTY LIST
Remote service used for meeting summaries
  • ce-voice-typing.speechify.com

    Speechify AI summarization endpoint that receives full meeting transcript content.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

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.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You choose the meeting email-share feature and enter recipient email addresses.

The extension did this

The extension posts the meeting name, generated summary, full formatted transcript, sender name, and optional share link through Speechify app backend.

02EvidenceFIELD TABLE
Fields in each email-share request
FieldValueWhy it matters
Recipient email
manager@example.comThe address you enter determines who receives the meeting email.
Sender name
Alex ReaderThe request can include the display name or email from the signed-in Speechify account.
Meeting name
Weekly roadmap syncThe email identifies the captured meeting by its tab title.
Generated summary
The 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 link
https://app.speechify.com/item/meeting-note-123?folder=abcIf a saved library item exists, the request can include a share URL for that item.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://app.speechify.com/api/meeting/email
One POST is made for each recipient; failures are collected from rejected promises.
Headers
Content-Typeapplication/json
AuthorizationBearer <redacted>
04EvidenceCODE COMPARE
The code that does this

Email-share UI route and backend POST

What it actually does
Sidepanel validates recipients and calls send-emailsidepanel/chunk-34T6TALS.js
const 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 backendoffscreen/src/main.js
let 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");
05EvidenceTHIRD PARTY LIST
Remote service used for email sharing
  • 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

Updated 30 September 2026ljflmlehinmoeknoonhibbjpldiijjmm