Is Stream Live — notifications for live streams safe?

High risk

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.

DomenOv2.30.1Chrome Web Store
72Risk

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
What actually happens
You did this

You start the Stream Live VKVideo sign-in flow.

The relevant page is live.vkvideo.ru with a #streamlive-signin hash.

The extension did this

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.

Fields assembled into the token upload
  • 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.

Captured request
POSThttps://streamlive.fadme.dev/api/vkplay/token
The code that does this

Exact code path from VKVideo storage to Stream Live API upload

Readable version

Readable equivalent of the VKVideo sign-in handler

content/content.js
const 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.js
const 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.js
function 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;  }}
External destination receiving the sign-in token
    • 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.

Updated 30 September 2026doepggdkcpgpoahobdnbppmbcfeplijh