Is Price tracker - Otsledit safe?

High risk

Otsledit is high risk. On every startup, and again every five hours, Otsledit's worker fetches config from otsledit.net/list; we captured HTTP 200 on load. The response controls affiliate links injected, which URLs get rewritten, and which selectors find prices.…

Price Trackerv3.5.8Chrome Web Store
77Risk
Who publishes it

Price Tracker - 1 other listing 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
Price Tracker

Same store account

1 other listing published from this account, 50k+ users between them. 1 of them carries a finding.

Shared hosts - 9 hostnames

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.

api.otsledit.net
Also called by 1 other listing: Price tracker - Otsledit
brozen.yandex.ru
Also called by 1 other listing: Price tracker - Otsledit
yabro1.zen-test.yandex.ru
Also called by 1 other listing: Price tracker - Otsledit
yabro2.zen-test.yandex.ru
Also called by 1 other listing
yandexmetrica.com
Also called by 1 other listing: Price tracker - Otsledit
otsledit.net
Also called by 2 other listings, including Calculator Extension
tns-counter.ru
Also called by 2 other listings
wextap.com
Also called by 4 other listings, including Calculator Extension
tatrck.com
Also called by 6 other listings, including Ghostery, Silent Adblock, Ghostery – Privacy Ad Blocker

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

Publishers can request a review.

Findings

Remote Config Fetch Controls Affiliate Link Injection

On every startup, and again every five hours, Otsledit's worker fetches config from otsledit.net/list; we captured HTTP 200 on load.

The response controls affiliate links injected, which URLs get rewritten, and which selectors find prices.

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

You install the extension and open Chrome, no further action required.

The extension did this

Otsledit fetches a remote configuration file from otsledit.net and stores the response in chrome.storage.local, then repeats the fetch every five hours.

The stored values directly control which affiliate links are injected into pages you visit and which URL patterns trigger rewrites.

Captured request
GEThttps://otsledit.net/list

HTTP 200 JSON document containing affiliate partner lists (liAff, loAff, liApi), URL rewrite patterns, DOM selector rules (pattern[].firstSelector, pattern[].secondSelector), and storage keys ads_link, ads_uni, srtLink, srtLayout. Confirmed via dynamic analysis: chrome.storage.local populated with ads_link=1, ads_uni=1, srtLink='', srtLayout='', partners=[] immediately after extension load.

Headers
Accept
application/json
Content-Type
application/json
When this fires

Every 5 hours

The config fetch recurs every five hours while Chrome is running, keeping the affiliate injection rules synchronized with the server without requiring an extension update.

Storage keys written from the remote config response
  • Affiliate partner list (ads_link)
    1

    Controls which affiliate links are injected into pages you browse.

  • Universal affiliate rules (ads_uni)
    1

    A second set of affiliate injection rules applied across all sites.

  • URL rewrite patterns (pattern)
    [{"mat":"allegro\.pl\/(.*?)","firstSelector":"[itemprop=\"price\"]"}]

    Regex rules that determine which shopping URLs get their links replaced with affiliate versions.

  • Search redirect link (srtLink)
    https://otsledit.net/rrr?url=

    URL to redirect search result links through, enabling affiliate attribution on search pages.

  • Partner list (partners)
    ["aliexpress", "amazon"]

    Array of affiliate partner identifiers pulled from the server's defaultSLink response.

The code that does this

Remote config fetch from background.js (deeplinks.js module, lines 87-118)

Readable version
// On extension load (deeplinks.js, background.js line 87)fetch("https://otsledit.net/list", {  method: 'GET',  headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }}).then(response => response.json()).then(data => {  if (data.defaultMatches && data.defaultLinks && data.defaultSLink &&      data.defaultLinks.length && data.defaultSLink.length) {    // Overwrite in-memory affiliate state    defaultMatches = data.defaultMatches;   // regex used to detect matching pages    defaultLinks   = data.defaultLinks;     // affiliate link list    defaultSLink   = data.defaultSLink;     // search result link list    defaultMinor   = data.defaultMinor;    window.liAff   = data.liAff;            // affiliate partner config    window.loAff   = data.loAff;    window.liApi   = data.liApi;    pattern        = data.pattern;          // DOM selector rules for price elements    // Write to persistent extension storage    newStorage['ads_link']   = data.adsLink;    newStorage['ads_uni']    = data.adsUni;    newStorage['pattern']    = JSON.stringify(pattern);    newStorage['srtLink']    = data.srtLink;    // search redirect URL    newStorage['srtLayout']  = data.srtLayout;    // Build partner list from server response    let partners = [];    for (let i = 0; i < defaultSLink.length; i++) {      if (defaultSLink[i][2]) partners.push(defaultSLink[i][0]);    }    newStorage['partners'] = partners;  }}).catch(error => console.log('Error query:', error));// Repeat every 5 hours (18,000,000 ms) while browser is runningsetInterval(function() {  fetch("https://otsledit.net/list", {    method: 'GET',    headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }  }).then(response => response.json()).then(data => {    if (data.defaultMatches && data.defaultLinks && data.defaultSLink &&        data.defaultLinks.length && data.defaultSLink.length) {      defaultMatches = data.defaultMatches;      defaultLinks   = data.defaultLinks;      defaultSLink   = data.defaultSLink;      defaultMinor   = data.defaultMinor;      window.liAff   = data.liAff;      window.loAff   = data.loAff;      window.liApi   = data.liApi;      pattern        = data.pattern;      newStorage['ads_link']   = data.adsLink;      newStorage['ads_uni']    = data.adsUni;      newStorage['pattern']    = JSON.stringify(pattern);      newStorage['srtLink']    = data.srtLink;      newStorage['srtLayout']  = data.srtLayout;    }  }).catch(error => console.log('Error query:', error));}, 5 * 60 * 60 * 1000);  // 18,000,000 ms
Remote host controlling affiliate injection behavior
    • otsledit.net

    Config endpoint (/list) delivering affiliate links and URL rewrite rules; also hosts install/uninstall tracking (/install, /rrr). Operated by the Otsledit developer.

Tab URL Rewritten Before Shopping Pages Finish Loading

We observed the extension rewrite the address bar on a matching shopping site before the page loads: a listener swaps the tab's destination for a server template first.

Fires once per domain roughly every 50 hours, non-paid accounts only.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You open a shopping-site URL that matches a pattern the extension's server currently supplies, for example keepa.com.

No click on the extension icon or any UI element is needed; a normal page navigation is enough.

The extension did this

The extension's background service worker intercepts the page load and rewrites the tab to a different URL before the original page appears.

The replacement URL is built by prepending a server-supplied template to the page you were navigating to.

Captured request
GEThttps://chrome.google.com/webstore/detail/price-tracker-otsledit/ibamclpibpnhmkaphhemfbljmenlpbch?v=https%3A%2F%2Fkeepa.com%2F

301 redirect, chaining through chromewebstore.google.com (302) to a 200 response. This is a top-level tab navigation triggered by chrome.tabs.update, not a page-initiated fetch, and no page on the original site could generate this link on its own, since it embeds the extension's own catalog ID as a parameter of itself.

Server-controlled inputs to the rewrite
  • Domain match pattern
    (^https:\/\/keepa\.com)

    A regular expression, supplied by the extension's server, that determines which sites get their tab URL rewritten.

  • Redirect template
    https://chrome.google.com/webstore/detail/price-tracker-otsledit/ibamclpibpnhmkaphhemfbljmenlpbch?v=

    The URL prefix the extension prepends to your original page URL to build the replacement tab destination.

  • Original page URL (carried through)
    https://keepa.com/

    The page you were navigating to, appended to the redirect template so it's preserved through the chain.

When this fires

Every 2 days

The domain-match patterns and redirect templates are pulled from a periodically-refreshed server response, so which sites get rewritten (and where the rewrite points) can change without an extension update. Once a given domain is rewritten, the extension will not rewrite that same domain again for about 50 hours.

The code that does this

The navigation listener and its rewrite gate (background.js + partner.js)

Readable version

Tab-update listener (annotated)

background.js
// Fires on every tab navigation while the page is still loadingchrome.tabs.onUpdated.addListener(function (tabId, changeInfo) {  if ("loading" == changeInfo.status) {    chrome.tabs.get(tabId, function (tab) {      if (chrome.runtime.lastError || !tab || tab.pinned) return;      storageReady.then(() => {        // canReload checks the URL against the server-supplied match        // pattern, the paid-tier flag, and the per-domain cooldown        if (canReload(tab.url)) {          // Replace the tab's destination BEFORE the original page loads          chrome.tabs.update(tabId, {            url: refUrl(tab.url)   // server template + original URL          }, () => chrome.runtime.lastError);        }      });    });  }});

canReload / refUrl gate (annotated)

partner.js
// Builds the replacement URL: server redirect template + original page URLfunction refUrl(originalUrl) {  window.links = checkLinks(); // cached server response (defaultLinks)  for (let i = 0; i < window.links.length; i++) {    if (originalUrl.indexOf(window.links[i][0]) > -1) {   // domain match      if (window.links[i][2]) {        return originalUrl + window.links[i][2];      } else {        return window.links[i][1] + encodeURIComponent(originalUrl); // template + original URL      }    }  }  return originalUrl; // no match: leave tab alone}// Decides whether THIS navigation should be rewrittenfunction canReload(url) {  let user = JSON.parse(newStorage['user'] || null);  // Only for accounts without a paid subscription (payment !== true)  let rewritesEnabled = parseInt(newStorage['ads_link']) && (user && user.payment == false || !user);  if (!url) return false;  let shop = getShop(url);       // resolve which shop config applies  if (!shop) return false;  let cooldownKey = "ls_" + shop;  let cooldownExpired = checkTime(cooldownKey);  let shouldRewrite = !url.match(/admitad/) && !url.match(/chrome-extension/)    && cooldownExpired && !!checkDeepLink(url, matches) && rewritesEnabled;  if (shouldRewrite) {    getUpdateLastSet(cooldownKey);  // start the ~50h cooldown for this domain    return true;  }  return false;}// Per-domain cooldown: true once 180,000 seconds (~50 hours) have elapsedfunction checkTime(key) {  let now = Math.floor(Date.now() / 1000);  let last = parseInt(newStorage[key]) || 0;  return !(now - last < 180000);}
Hosts involved in the rewrite
    • otsledit.net

    Serves the domain-match patterns and redirect templates that drive which shopping sites get their tab URL rewritten. Operated by the extension developer.

    • chrome.google.com / chromewebstore.google.com

    Observed as the redirect destination in our test session; the extension's redirect template pointed here with the original page URL attached as a query parameter.

Tracked page URL sent to Otsledit logging endpoint on save

When you create a new price-tracking item, the extension sends the tracked page URL to api.otsledit.net/log-track.

The save-track flow posts JSON containing the URL and page domain to that remote endpoint.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

The user creates a new price-tracking item.

The extension did this

The extension posts the tracked page URL and domain to api.otsledit.net/log-track.

Captured request
POSThttps://api.otsledit.net/log-track
Fields in the request
  • Tracked page URL
    https://example.com/product/123

    This identifies the page the user chose to track, which can reveal browsing context tied to the tracking action.

Updated 30 September 2026ibamclpibpnhmkaphhemfbljmenlpbch