Is Unfollower Tracker for Top Photo App safe?
Unfollower Tracker for IG receives server-controlled extraction patterns and can open arbitrary URLs on command from besocial.top.
Each time the popup opens, the extension contacts besocial.top to check its license token. The server response can supply updated regex-like delimiters that the extension stores and uses to extract the Instagram CSRF token and username from inline page scripts. The same response can also supply an open_link URL, which the extension immediately opens in a background tab with no validation.
Who publishes itBeSocialTools - 4 other listings from the same operator, none carrying a finding
BeSocialTools - 4 other listings from the same operator, none carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
4 other listings published from this account, 11k+ users between them, none of them carrying a finding.
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.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Server-Supplied Rules Extract Instagram Session Fields
Checking Instagram account status asks besocial.top for tokenizer rules and saves the returned CSRF-token and username delimiters.
The content script applies those to Instagram scripts, so the remote response decides what text is captured.
What actually happens
You open the extension while it checks an Instagram page.
The extension asks besocial.top for tokenizer rules, stores the returned delimiters, and uses them to read Instagram session fields from page scripts.
Captured request
Confirmed response JSON contained csrf_tokenizer and user_tokenizer objects with start and end delimiters.
Fields controlled or extracted by the tokenizer flow
- CSRF-token delimiterscsrf_tokenizer.start=',{"csrf_token":"' and csrf_tokenizer.end='"},'
These rules decide where the extension starts and stops reading the Instagram CSRF token from page script text.
- Username delimitersuser_tokenizer.start='"username":"' and user_tokenizer.end='","'
These rules decide where the extension starts and stops reading the Instagram username from page script text.
- Instagram CSRF tokenHcT6rNp9vQ2mX4sLa8Kb3YdF0wJe1ZuG (illustrative)
This credential-related value is tied to an active Instagram session and can be used by code running in the extension.
- Instagram usernameacme.social.ops (illustrative)
This links the extracted session data to a specific Instagram account.
What's stored on your device
The saved tokenizer objects become the rules used during the next Instagram page-script scan.
- Location
- chrome.storage.local keys csrf_tokenizer and user_tokenizer
{ "default_csrf_tokenizer": { "end": "\"},", "start": ",{\"csrf_token\":\"" }, "default_user_tokenizer": { "end": "\",\"", "start": "\"username\":\"" }, "server_response_handling": "If the response contains csrf_tokenizer or user_tokenizer, popup.js writes those objects to chrome.storage.local and later sends them to content.js."}The code that does this
Remote rules are fetched, stored, sent to the content script, and applied to Instagram scripts
Remote rule fetch
popup/popup.jscheck_pro: async function() { let t = this, e = chrome.runtime.getManifest().version, n = i.g + "check-token.php?token=" + t.token + "&version=" + e + "&chn=" + i.b, r = null; try { r = await fetch(n, { muteHttpExceptions: !0 }) } catch (e) { return console.log(e), void t.$emit("server_error", e) } const a = await r.json(); let s = a; 0 == a.premium ? t.is_pro = !1 : t.is_pro = !0, t.is_pro_status_init = !0, t.is_pro || t.is_trial || !a.trial || t.has_trial_show || (t.offer_trial_end = a.trial.trial_end, t.$emit("trial_offer", a.trial), t.has_trial_show = !0), t.is_sub = "1" == a.sub_status, a.notice && t.notice != a.notice && (s.notice_dialog = !0), s.is_pro = t.is_pro || t.is_trial, s.is_sub = t.is_sub, a.open_link && t.open_link != a.open_link && (t.open_link = a.open_link, chrome.tabs.create({ url: t.open_link, active: !1 })), a.more_tools && (t.more_tools = a.more_tools), t.$emit("server", s)}Server response stores tokenizer objects
popup/popup.json_server_response: function(t) { let e = this; e.is_pro = t.is_pro, e.is_sub = t.is_sub, t.more_tools && (e.more_tools = t.more_tools), e.notice = t.notice, e.notice_dialog = t.notice_dialog, e.server_response = t, t.csrf_tokenizer && chrome.storage.local.set({ csrf_tokenizer: t.csrf_tokenizer }, t => {}), t.user_tokenizer && chrome.storage.local.set({ user_tokenizer: t.user_tokenizer }, t => {})}Popup sends stored tokenizers to the content script
popup/popup.jsconnect_content: function() { let t = this, e = chrome.tabs.connect(t.ig_tab_id); e.onMessage.addListener((function(e) { "GetInsStatusRsp" == e.type && (console.log(JSON.stringify(e)), t.loading_profile_done = !0, e.is_login && e.username ? (t.ig_username = e.username, t.ig_avatar = e.avatar, t.csrf_token = e.csrf_token, t.igapi = new v(e.csrf_token), t.ig_state = t.IG_READY, chrome.tabs.remove([t.ig_tab_id], (function() {})), clearInterval(t.conn_timer), t.on_ig_ready()) : (t.ig_state = t.IG_NOT_LOGIN, clearInterval(t.conn_timer), t.show_login_required_dialog = !0)) })), chrome.storage.local.get(["csrf_tokenizer", "user_tokenizer"], n => { let r = n.csrf_tokenizer || t.default_csrf_tokenizer, i = n.user_tokenizer || t.default_user_tokenizer; e.postMessage({ type: "GetInsStatusReq", csrf_tokenizer: r, user_tokenizer: i }) })}Content script extracts fields with supplied delimiters
content.jsfunction n(e, t, n) { let r = e.indexOf(t), o = e.indexOf(n, r + t.length); return r > 0 && o >= 0 ? e.substr(r + t.length, o - r - t.length) : ""}let r = 0;chrome.runtime.onConnect.addListener((function(e) { e.onMessage.addListener((function(t) { if ("GetInsStatusReq" == t.type) { let o = !document.querySelector(".not-logged-in"), i = "", u = "", l = ""; if (o) { let e = document.querySelectorAll("script"); for (let o = 0; o < e.length; o++) { if (e[o].text.startsWith("window._sharedData = ")) { r = 1; let t = "window._sharedData = ".length, n = JSON.parse(e[o].text.substr(t, e[o].text.length - 1 - "window._sharedData = ".length)); i = n.config.viewer.username, u = n.config.viewer.profile_pic_url; break } r = 2; let s = n(e[o].text, t.csrf_tokenizer.start, t.csrf_tokenizer.end); s && (l = s); let a = n(e[o].text, t.user_tokenizer.start, t.user_tokenizer.end); if (a && (i = a), l && i) break } } e.postMessage({ type: "GetInsStatusRsp", is_login: o, username: i, avatar: u, csrf_token: l, v: r }) } })), e.postMessage({ type: "Hello" })}))}Remote host involved in this tokenizer flow
- besocial.top
Receives the extension token/version/channel check request and returns tokenizer delimiter objects used for Instagram script parsing.
Where it sends data
Destinations our analysis observed Unfollower Tracker for Top Photo App 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.
- besocial.top
Unfollower Tracker for Top Photo App sends data to besocial.top. 2 other extensions we have analyzed send data here.
