Is Поток – ускоритель YouTube safe?

Medium risk

Поток is medium risk. Turning the YouTube accelerator on has the extension ask its service for a proxy host/port, then install a rule routing YouTube, googlevideo, ytimg, ggpht traffic through it. The lookup POST returned proxy details.…

TV Rainv2.0.5Chrome Web Store
45Risk
Who publishes it

NAOBOROT LLP - 3 other listings from the same operator, none carrying a finding

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
TV Rain
Declared legal entity
NAOBOROT LLP
Registered address
3rd Floor Suite, 207 Regent Street, London W1B 3HH, GB
Registered contact
Naoborot LLP

Same operator - 3 listings

Published under a different store account, but sharing the registered address, contact or declared legal entity this one gave the store.

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

YouTube traffic routes through service-provided proxies

Turning the YouTube accelerator on has the extension ask its service for a proxy host/port, then install a rule routing YouTube, googlevideo, ytimg, ggpht traffic through it.

The lookup POST returned proxy details.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You press the extension's main button to connect the YouTube accelerator.

The popup writes the connected target state when the connection timer still allows a session.

The extension did this

The service worker requests proxy details and configures Chrome to route YouTube-related traffic through the returned proxy.

The PAC script exempts music.youtube.com but routes googlevideo.com, youtube.com, ytimg.com, and ggpht.com.

02EvidenceNETWORK CAPTURE
Captured request
POSThttps://ptkdfjnflmpalkfpkehjgopmjldhjhgb.click/api/v1/get-proxy
Dynamic analysis observed this proxy-lookup request returning proxy server details with a host and port.
Headers
Content-Typeapplication/json
03EvidenceFIELD TABLE
Traffic the PAC script sends through the returned proxy
FieldValueWhy it matters
YouTube pages
https://www.youtube.com/watch?v=dQw4w9WgXcQ (illustrative)Your YouTube page visits sit on the proxy path once connected, revealing which host you reach; visibility depends on proxy/TLS handling.
Video stream requests
https://rr3---sn-n8v7zn7s.googlevideo.com/videoplayback?id=o-AJirM2F6x9 (illustrative)Video playback connects through googlevideo.com hosts YouTube selects; those connections can show streaming is happening through the proxy.
Thumbnail and image hosts
https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg (illustrative)Your YouTube image and thumbnail loads are also matched by the proxy rule, adding more page-view context to the proxy path.
Music YouTube exception
https://music.youtube.com/watch?v=dQw4w9WgXcQ (illustrative)Music YouTube traffic is excluded from the proxy rule, so routing targets only other YouTube-related hosts named in the PAC script.
04EvidenceCODE COMPARE
The code that does this

The service worker fetches a proxy and installs a YouTube PAC rule

What it actually does
Readable service-worker proxy workflowbackground/service_worker.js
    async function b() {
      try {
        const {
          extensionData: e
        } = await y(w.ExtensionData);
        if (void 0 !== e) return e;
        const t = await fetch("https://storage.googleapis.com/potok/potok.json");
        return await t.json()
      } catch (e) {
        console.error("Error loading configuration:", e), await m({
          currentState: "error"
        })
      }
    }
    async function v({
      onFail: e
    } = {}) {
      try {
        await m({
          currentState: "connecting"
        });
        const t = await b();
        if (!t) return;
        const n = t.apiBaseUrl,
          r = await async function() {
            const {
              deviceId: e
            } = await f(p.DeviceId);
            return e || await async function() {
              const e = "xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (e => {
                const t = 16 * Math.random() | 0;
                return ("x" === e ? t : 3 & t | 8).toString(16)
              }));
              return await m({
                deviceId: e
              }), e
            }()
          }();
        r || console.warn("enableProxy: Failed to retrieve device ID, proceeding without it.");
        const o = await async function() {
          try {
            const e = await fetch("https://api.ipify.org?format=json");
            return (await e.json()).ip
          } catch (e) {
            return console.error("Error fetching public IP:", e), null
          }
        }();
        null === o && console.warn("enableProxy: Failed to retrieve public IP address, proceeding without it.");
        const i = await async function({
          apiBaseUrl: e,
          deviceId: t,
          deviceIp: n,
          onFail: r
        }) {
          const o = await fetch(`https://${e}/api/v1/get-proxy`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              device_id: t ?? "unknown",
              device_ip: n ?? "unknown",
              on_fail: r
            })
          });
          return await o.json()
        }({
          apiBaseUrl: n,
          deviceId: r,
          deviceIp: o,
          onFail: e
        }), {
          host: a,
          port: s
        } = i;
        chrome.proxy.settings.set({
          value: {
            mode: "pac_script",
            pacScript: {
              data: `\n              function FindProxyForURL(url, host) {\n                  if (dnsDomainIs(host, ".music.youtube.com")) {\n                    return "DIRECT";\n                  }\n\n                  if (dnsDomainIs(host, ".googlevideo.com") ||\n                      dnsDomainIs(host, ".youtube.com") ||\n                      dnsDomainIs(host, ".ytimg.com") ||\n                      dnsDomainIs(host, ".ggpht.com")) {\n                      return "PROXY ${a}:${s}";\n                  }\n                  return "DIRECT";\n              }\n            `
            }
          },
          scope: "regular"
        }, (async () => {
          if (chrome.runtime.lastError) await m({
            currentState: "error"
          }), console.error("Error setting proxy:", chrome.runtime.lastError.message);
          else {
            const {
              connectionCount: e,
              targetState: t
            } = await f([p.ConnectionCount, p.TargetState]), n = e ?? 0;
            "disconnected" === t ? S() : await m({
              currentState: "connected",
              connectionCount: n + 1
            })
          }
        }))
      } catch (e) {
        console.error("Error loading configuration:", e), await m({
          currentState: "error"
        })
      }
    }
05EvidenceTHIRD PARTY LIST
External services involved before the PAC rule is installed
  • storage.googleapis.com

    Hosts the potok.json configuration that names the extension service domain.

  • ptkdfjnflmpalkfpkehjgopmjldhjhgb.click

    Receives the proxy-lookup POST and returns the proxy host and port used by the PAC script.

  • api.ipify.org

    Receives a public-IP lookup before the proxy-selection request is made.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI SANDBOX

Remote JSON Config Controls Which Server Receives Your Device ID and IP

On every startup, the extension fetches config from storage.googleapis.com/potok/potok.json.

Its apiBaseUrl decides where your device ID and IP go on connect.

Four GETs confirmed ptkdfjnflmpalkfpkehjgopmjldhjhgb.click received the data.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open your browser with the extension installed.

The extension did this

The extension fetches a JSON config from Google Cloud Storage and reads apiBaseUrl. Later requests carrying your device ID and public IP go to the domain named there.

The config can change at any time without an extension update, any domain can be placed in apiBaseUrl.

02EvidenceNETWORK CAPTURE
Captured request
GEThttps://storage.googleapis.com/potok/potok.json
Returns JSON: {"apiBaseUrl": "ptkdfjnflmpalkfpkehjgopmjldhjhgb.click"}. The value of apiBaseUrl becomes the target host for all device_id and device_ip submissions.
03EvidenceCODE COMPARE
The code that does this

Config-loading code from the extension's shipping source:

What it actually does
Config loader
// Loads the remote configuration. Returns cached session copy if available.
async function loadConfig() {
  try {
    // Check session storage cache first
    const { extensionData } = await chrome.storage.session.get('extensionData');
    if (extensionData !== undefined) return extensionData;

    // Fetch from Google Cloud Storage — no SRI, no domain validation
    const response = await fetch('https://storage.googleapis.com/potok/potok.json');
    return await response.json();
    // Returned object contains: { apiBaseUrl: "<hostname>" }
    // This hostname is used directly for the POST /api/v1/get-proxy call
  } catch (e) {
    console.error('Error loading configuration:', e);
    await chrome.storage.local.set({ currentState: 'error' });
  }
}
04EvidencePLAIN NOTE
What a config change means for you

Because the API base URL is fetched fresh from storage.googleapis.com/potok/potok.json on every browser startup, the owner of that file can point the data recipient to any domain at any time. Users receive no notification when this changes. The extension does not validate the returned domain against any allowlist, and there is no Subresource Integrity (SRI) check on the fetched JSON.

05EvidenceTHIRD PARTY LIST
Hosts involved in the remote config chain:
  • storage.googleapis.com

    Google Cloud Storage bucket 'potok' serving the runtime config file potok.json. Controls the destination for all device data.

  • ptkdfjnflmpalkfpkehjgopmjldhjhgb.click

    Proxy API endpoint resolved from potok.json at the time of our test session. Received POST requests with device_id and device_ip.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Persistent Device UUID Stored and Reused Across All Browser Sessions

On first connect, the extension makes a UUID, stored as 'deviceId'.

It persists across restarts and rides every proxy-API connect.

Confirmed written on first use and unchanged in later POSTs, letting the server link all your sessions.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You click connect for the first time after installing the extension.

The extension did this

The extension makes a UUID and saves it to storage.local. Every later connect, in any future session, retrieves and sends this same UUID to the proxy API.

There is no mechanism in the extension UI to view, reset, or delete this identifier.

02EvidenceSTORAGE DUMP
What's stored on your device

Written after your first connect, unchanged across restarts. Appears verbatim as device_id on every POST to /api/v1/get-proxy.

Locationchrome.storage.local key 'deviceId'
Contents
{"deviceId":"b691591-2e2b-45f5-8a34-f3ee0f36967d"}
03EvidenceCODE COMPARE
The code that does this

UUID generation and storage from the extension's shipping source:

What it actually does
Device ID retrieval / generation
// Retrieves the stored device ID, or generates and stores a new one.
const deviceId = await (async () => {
  const { deviceId } = await chrome.storage.local.get('deviceId');
  if (deviceId) return deviceId;  // reuse existing UUID

  // Generate a new UUID via Math.random() (not crypto.getRandomValues)
  const newId = 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = Math.random() * 16 | 0;
    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  });
  await chrome.storage.local.set({ deviceId: newId });  // persists across sessions
  return newId;
})();
04EvidenceARTIFACT
Check if you're affected

Reads the 'deviceId' stored by the Поток extension from chrome.storage.local via the Chrome command-line debugger. Run this to confirm whether the extension has assigned a persistent identifier to your browser.

RequiresPython 3.8+pip install websocketsChrome with --remote-debugging-port=9222
check-potok-device-id.sh · sh
#!/usr/bin/env bash
# check-potok-device-id.sh
# Reads the deviceId from Поток's chrome.storage.local.
# Requires Chrome with remote debugging enabled.
#
# Usage:
#   1. Launch Chrome with: --remote-debugging-port=9222
#   2. Run: bash check-potok-device-id.sh

EXT_ID="ocionjkjaapcailghfnhnjkpapepelib"
DEBUG_URL="http://localhost:9222"

# Find the service worker target for this extension
SW_URL=$(curl -s "${DEBUG_URL}/json" | python3 -c "
import sys, json
for t in json.load(sys.stdin):
    if '${EXT_ID}' in t.get('url','') and t.get('type') == 'service_worker':
        print(t['webSocketDebuggerUrl'])
        break
")

if [ -z "$SW_URL" ]; then
  echo "ERROR: Could not find Поток service worker. Is the extension installed and Chrome running with --remote-debugging-port=9222?"
  exit 1
fi

echo "Querying storage via: $SW_URL"

# Evaluate chrome.storage.local.get('deviceId') in the service worker context
python3 << PYEOF
import asyncio, json, websockets

async def read_storage():
    async with websockets.connect('${SW_URL}') as ws:
        await ws.send(json.dumps({
            'id': 1,
            'method': 'Runtime.evaluate',
            'params': {
                'expression': 'chrome.storage.local.get("deviceId").then(r => JSON.stringify(r))',
                'awaitPromise': True
            }
        }))
        result = json.loads(await ws.recv())
        value = result.get('result', {}).get('result', {}).get('value', 'null')
        data = json.loads(value)
        if 'deviceId' in data:
            print(f'[FOUND] deviceId = {data["deviceId"]}')
        else:
            print('[NOT FOUND] No deviceId stored yet. Connect the extension first.')

asyncio.run(read_storage())
PYEOF
How to run it
  1. 1
    Launch Chrome with remote debugging: google-chrome --remote-debugging-port=
  2. 2
    9
  3. 3
    2
  4. 4
    2
  5. 5
  6. 6
    Install the Поток extension and click connect once.
  7. 7
    Run: bash check-potok-device-id.sh.
  8. 8
    The output shows your assigned device ID.

What it can do

Permissions this extension asks for, as declared in version 1.1.1. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 2.0.5, which we have not unpacked yet.

  • Read and change your data on youtube.com

    https://*.youtube.com/*

  • Route all of your browsing through a server of its choosing

    proxy

  • Store data in your browser

    storage

  • Watch every request your browser makes

    webRequest

  • Act on the current tab, but only after you click the extension

    activeTab

  • See the address and title of every tab you have open

    tabs

Updated 30 September 2026ocionjkjaapcailghfnhnjkpapepelib