Is Price tracker - Otsledit safe?
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.…
Who publishes itPrice Tracker - 1 other listing from the same operator, 1 of them carrying a finding
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.
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.
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
You install the extension and open Chrome, no further action required.
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.
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.
- Accept
- application/json
- Content-Type
- application/json
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.
- 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.
Remote config fetch from background.js (deeplinks.js module, lines 87-118)
// 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- 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
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'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.
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.
- 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 templatehttps://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.
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 navigation listener and its rewrite gate (background.js + partner.js)
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);}- 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
The user creates a new price-tracking item.
The extension posts the tracked page URL and domain to api.otsledit.net/log-track.
- Tracked page URLhttps://example.com/product/123
This identifies the page the user chose to track, which can reveal browsing context tied to the tracking action.