Is Video to MP3 safe?
Video to MP3 uploads user-selected converted audio for AI transcription.
When a user chooses the transcription feature after converting a video, the extension sends the converted MP3 audio to a Supabase edge function. That function forwards the audio to Pollinations for AI transcription, even though the packaged privacy page says conversions stay local and third-party services are not used.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Converted MP3 audio is uploaded for transcription
Pressing Transcribe on a converted file, the extension reads the MP3, encodes it, and sends it to a Supabase endpoint that forwards the audio to Pollinations, though the privacy page says files stay on-device and no third parties are used.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You click Transcribe after a video has been converted to an MP3 file.
The button is shown next to the completed conversion result in the popup.
The extension uploads the converted audio content to a remote transcription endpoint.
The packaged server-function source forwards that audio data to Pollinations for transcription.
- Selected audio contentbase64-encoded MP3 audio from a converted file
Audio from your selected file is sent for remote transcription, so spoken words or background audio can leave your device.
- Audio formatmp3
This tells the transcription service how to interpret the uploaded audio file.
Dynamic analysis confirmed this request after the Transcribe action; the bundled function returns JSON containing a transcription on success.
- apikey
- <redacted>
- Content-Type
- application/json
- Authorization
- Bearer <redacted>
Popup code turns the converted MP3 into an upload
Transcribe button handler
popup.jsfunction createConversionItem(file, fileId) { const conversionItem = conversionTemplate.content.cloneNode(true).querySelector('.conversion-item'); conversionItem.id = `conversion-${fileId}`; const fileName = conversionItem.querySelector('.file-name'); const displayName = file.name.split('\\').pop().split('/').pop(); fileName.textContent = displayName; fileName.title = displayName; const fileSize = conversionItem.querySelector('.file-size'); fileSize.textContent = formatFileSize(file.size); const removeBtn = conversionItem.querySelector('.remove-btn'); removeBtn.addEventListener('click', () => removeConversion(fileId)); conversionsContainer.appendChild(conversionItem); const downloadBtn = conversionItem.querySelector('.download-btn'); downloadBtn.addEventListener('click', () => downloadConvertedFile(fileId)); const transcribeBtn = conversionItem.querySelector('.transcribe-btn'); transcribeBtn.addEventListener('click', () => runIfProOrPrompt(() => transcribeAudio(fileId))); const highQualityBtn = conversionItem.querySelector('.high-quality-btn'); highQualityBtn.addEventListener('click', () => runIfProOrPrompt(() => showFormatModal(fileId))); return conversionItem;}MP3 blob encoded and posted
popup.jsasync function transcribeAudio(fileId) { if (!activeConversions.has(fileId)) return; const conversionInfo = activeConversions.get(fileId); if (conversionInfo.status !== 'completed' || !conversionInfo.result) return; const conversionItem = document.getElementById(`conversion-${fileId}`); if (!conversionItem) return; openTranscribeModal(); try { const mp3Blob = conversionInfo.result; const base64Audio = await blobToBase64(mp3Blob); const response = await fetch(`${SUPABASE_URL}/functions/v1/transcribe`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'apikey': SUPABASE_KEY, 'Authorization': `Bearer ${SUPABASE_KEY}`, }, body: JSON.stringify({ audioData: base64Audio, audioFormat: 'mp3' }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`); } const result = await response.json(); if (result.error) throw new Error(result.error); const transcription = result.transcription || 'No transcription available'; currentTranscription = transcription; currentTranscriptionFileName = conversionInfo.fileName; showTranscriptionResult(transcription); } catch (error) { alert(`Ошибка транскрибации: ${error.message}`); closeTranscribeModal(); }}function blobToBase64(blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const base64 = reader.result.split(',')[1]; resolve(base64); }; reader.onerror = reject; reader.readAsDataURL(blob); });}Bundled Supabase function forwards the same audio to Pollinations
const POLLINATIONS_URL = "https://text.pollinations.ai/openai";serve(async (req) => { if (req.method === 'OPTIONS') { return new Response('ok', { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Methods': 'POST, OPTIONS' } }); } const body = await req.json(); const { audioData, audioFormat } = body; if (!audioData || !audioFormat) { return new Response(JSON.stringify({ error: 'Missing audioData or audioFormat' }), { status: 400 }); } if (!audioData.match(/^[A-Za-z0-9+/]*={0,2}$/)) { return new Response(JSON.stringify({ error: 'Invalid base64 audio data format' }), { status: 400 }); } if (audioData.length > 10 * 1024 * 1024) { return new Response(JSON.stringify({ error: 'Audio file too large. Please use a shorter audio file.' }), { status: 400 }); } const payload = { model: "gpt-4o-mini-audio-preview", messages: [ { role: "user", content: [ { type: "input_text", text: "Transcribe this audio and return only the transcript text." }, { type: "input_audio", input_audio: { data: audioData, format: audioFormat, }, }, ], }, ], }; const response = await fetch(POLLINATIONS_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${POLLINATIONS_TOKEN}` }, body: JSON.stringify(payload), }); const result = await response.json(); const transcription = result?.choices?.[0]?.message?.content; return new Response(JSON.stringify({ transcription }));});- sduzciunliptkkkezbzj.supabase.co
Receives the extension popup's transcription POST containing the base64 MP3 audio and mp3 format marker.
- text.pollinations.ai
Receives the packaged function's OpenAI-compatible transcription request with input_audio.data set to the same audioData value.
Packaged privacy text says files stay local
The packaged privacy page states that the extension does not collect, store, or transmit personal data; says all conversions occur locally; says files never leave the device; says files are not uploaded to external servers or third-party services; and says the extension does not integrate with or rely on third-party services.What it can do
Permissions this extension asks for, as declared in version 2.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Read and change your data on sduzciunliptkkkezbzj.supabase.co
https://sduzciunliptkkkezbzj.supabase.co/*
Read and change your data on nikuson-mp3-b08f.twc1.net
https://nikuson-mp3-b08f.twc1.net/*
Store data in your browser
storage
Where it sends data
Destinations our analysis observed Video to MP3 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.
- sduzciunliptkkkezbzj.supabase.co
Video to MP3 sends data to sduzciunliptkkkezbzj.supabase.co. No other extension we have analysed sends data here.
- text.pollinations.ai
Video to MP3 sends data to text.pollinations.ai. No other extension we have analysed sends data here.