Is Stream Live — notifications for live streams safe?
Stream Live sends VKVideo localStorage auth data to its backend during the sign-in flow.
When a user signs in on live.vkvideo.ru through the Stream Live flow, the extension reads localStorage values named auth and _clientId. It packages those values as a VK Play token and sends them to streamlive.fadme.dev with the extension's uuid token in the Authorization header.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
VKVideo sign-in token is posted to Stream Live
On live.vkvideo.ru with the #streamlive-signin hash, the content script reads VKVideo storage entries auth and _clientId, wraps them into a token message, and posts it to streamlive.fadme.dev/api/vkplay/token, tagged with extension UUID.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-522
- Source
- Dynamic sandbox
You start the Stream Live VKVideo sign-in flow.
The relevant page is live.vkvideo.ru with a #streamlive-signin hash.
The extension reads VKVideo sign-in storage and sends it to Stream Live.
The service worker posts the resulting vkplay token to streamlive.fadme.dev.
- VKVideo sign-in token(illustrative) {"access_token":"vk1.a.K8v2R9pQ6mN4sT1xW7zL0cB3yD5fH8jM2nP4qR6sT9vX1z","expires_in":86400}
This is credential material from your signed-in VKVideo session. Sending it off the VKVideo page can expose access tied to that session.
- VKVideo browser ID(illustrative) 0f0f2a4c-11e0-4d7f-8d27-0b2d1e3fa941
This browser-local identifier links the sign-in request to the same VKVideo browser profile.
- Extension install token(illustrative) m8yk2clka42p9n9y3xd3tkst8
This identifies the Stream Live extension installation that sends the token upload.
Exact code path from VKVideo storage to Stream Live API upload
Readable equivalent of the VKVideo sign-in handler
content/content.jsconst REQUEST_SET_TOKEN = "REQUEST_SET_TOKEN";const SERVICE_VKPLAY = "vkplay";const VKVIDEO_ORIGIN = "https://live.vkvideo.ru";function handleVkplaySignin() { if (!location.hash.startsWith("#streamlive-signin")) return; const cookieDomain = "." + VKVIDEO_ORIGIN.split("//")[1]; const authKey = "auth"; const clientIdKey = "_clientId"; const signInSelector = "[class*=TopMenuRightUnAuthorized_signInIcon]"; const authValue = localStorage.getItem(authKey); const clientId = localStorage.getItem(clientIdKey); if (!authValue || document.querySelector(signInSelector)?.parentElement) { const style = document.createElement("style"); style.innerHTML = "\n @keyframes blink-shadow {\n 0%, 100% {\n box-shadow: none;\n }\n 50% {\n box-shadow: 0 0 4px 8px #ed4444;\n }\n }\n\n .blinking-shadow {\n animation: blink-shadow 1s infinite;\n }\n "; document.head.appendChild(style); document.querySelector(signInSelector)?.parentElement?.classList.add("blinking-shadow"); setTimeout(() => { document.querySelector(signInSelector)?.parentElement.click(); setInterval(() => { if (!document.querySelector(signInSelector)?.parentElement) location.reload(); }, 1000); }, 500); return; } chrome.runtime.sendMessage({ event: REQUEST_SET_TOKEN, data: { service: SERVICE_VKPLAY, token: JSON.stringify({ ...JSON.parse(authValue), clientId }) } }).then(() => { const replacementClientId = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, char => { const random = Math.random() * 16 | 0; return (char === "x" ? random : (random & 3) | 8).toString(16); }); document.cookie = authKey + "=;path=/;domain=" + cookieDomain + ";expires=Thu, 01 Jan 1970 00:00:01 GMT;"; document.cookie = clientIdKey + "=;path=/;domain=" + cookieDomain + ";expires=Thu, 01 Jan 1970 00:00:01 GMT;"; localStorage.removeItem(authKey); localStorage.setItem(clientIdKey, replacementClientId); location.href = "https://streamlive.fadme.dev/vkplay/oauth2"; }).catch(error => logger.error("vkplayRun: " + error?.message + "\n" + error?.stack));}Readable equivalent of the token upload helper
background/background.jsconst UUID_TOKEN_KEY = "uuid_token";const API_BASE = "https://streamlive.fadme.dev/api";const REQUEST_TIMEOUT_MS = 60000;function makeHeaders(uuidToken) { return { Authorization: uuidToken, "Content-Type": "application/json" };}async function getOrCreateUuidToken() { let token = await syncStorage.get(UUID_TOKEN_KEY); if (!token) { const signupResponse = await fetch(API_BASE + "/signup", { method: "POST", signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) }); if (!signupResponse.ok) { token = Date.now().toString(36) + Math.floor(1e16 * Math.random()).toString(36) + Math.floor(1e11 * performance.now()).toString(36); const fallbackResponse = await fetch(API_BASE + "/signup", { method: "POST", headers: makeHeaders(token), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) }); if (!fallbackResponse.ok) throw new Error("signup failed"); await syncStorage.set(UUID_TOKEN_KEY, token); return token; } token = (await signupResponse.json()).token; await syncStorage.set(UUID_TOKEN_KEY, token); } return token;}const api = { async sendToken(service, token) { const uuidToken = await getOrCreateUuidToken(); const response = await fetch(API_BASE + "/" + service + "/token", { method: "POST", headers: makeHeaders(uuidToken), body: JSON.stringify({ token }), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) }); return response.ok; }};Readable equivalent of the REQUEST_SET_TOKEN dispatch branch
background/background.jsfunction handleRuntimeMessage(message, sender, sendResponse) { switch (message.event) { case REQUEST_SET_TOKEN: (async function submitToken(data) { try { setLoading(true); if (await api.sendToken(data.service, data.token)) { await refreshBroadcastsAndSettings(); return; } await refreshFromCache(); } finally { setLoading(false); } })(message.data); return; }}- streamlive.fadme.dev
Receives the vkplay token POST and also serves the Stream Live API used by this extension.
Where it sends data
Destinations our analysis observed stream-live-%E2%80%94-notificatio 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.
- streamlive.fadme.dev
stream-live-%E2%80%94-notificatio sends data to streamlive.fadme.dev. No other extension we have analysed sends data here.