Is TG Sender - TG bulk message send and invite safe?
Telegram Sender is high risk. TG Sender records workflow details via its bundled Aliyun logging helper. Dynamic analysis confirmed Start Inviting reaches the invite logger with the group link and username list; source also passes message text and scraped profile URLs.
Who publishes itadtargetinghello - 3 other listings from the same operator, 1 of them carrying a finding
adtargetinghello - 3 other listings 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
3 other listings published from this account, 207k+ users between them. 1 of them carries a finding.
Shared hosts - 2 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
Telegram workflow data is posted to an Aliyun logstore
TG Sender records workflow details via its bundled Aliyun logging helper.
Dynamic analysis confirmed Start Inviting reaches the invite logger with the group link and username list; source also passes message text and scraped profile URLs.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You start a Telegram automation workflow in the extension.
The confirmed path is the Start Inviting flow; the same logging helper is also called from bulk messaging and group scraping code paths.
The extension sends the workflow fields and extension metadata to an Aliyun logging endpoint.
Dynamic analysis observed the API invite path reaching the logger with a group link and comma-separated username list.
- Message textmessage: Follow up after the webinar today (illustrative)
Shows the content you entered for an automated Telegram message.
- Recipient usernamesusernames: @alice_example,@team_updates (illustrative)
Shows which Telegram accounts or phone-style entries you targeted in the workflow.
- Group linkgroup_link: https://t.me/example_group (illustrative)
Identifies the Telegram group or channel you selected for the invite workflow.
- Scraped profile URLuser: https://web.telegram.org/k/#123456789 (illustrative)
Identifies a Telegram profile URL returned by the group-scraping workflow.
- Extension metadatauuid: 8f4d2c91-0b62-4f5a-9b7c-2e1d03a4c5f6 (illustrative)
Links separate Telegram workflow records back to the same browser installation.
Confirmed during dynamic analysis; request body was not recorded in the verification evidence.
The logger builds the Aliyun payload and merges workflow fields
Shared Aliyun log helper
background.jsfunction sendLog(eventType, extraFields) { const client = new AliyunLogger({ host: "us-west-1.log.aliyuncs.com", project: "extension-us", time: 0.05, count: 1, logstore: "scrm-tg-sender" }); chrome.storage.local.get(null, (storage) => { const browserInfo = storage.browserInfo !== undefined ? storage.browserInfo : ""; const payload = { event_source: 1, event_type: eventType, event_time: Math.round(Date.now() / 1000), install_time: storage.installTime !== undefined ? storage.installTime : "", platform: browserInfo ? browserInfo.platform : "", user_agent: browserInfo ? browserInfo.userAgent : "", browser_type: browserInfo ? browserInfo.browser : "", uuid: storage.uuid !== undefined ? storage.uuid : "", version: chrome.runtime.getManifest().version, language: browserInfo ? browserInfo.language : "", plan_code: storage.plan_code !== undefined ? storage.plan_code : "" }; if (extraFields) { for (const key in extraFields) { payload[key] = extraFields[key]; } } client.send(payload); });}Bulk messaging passes message text and usernames
background.jsasync function startBulkMessage() { running = true; const recipients = settings["message-usernames"] !== undefined ? settings["message-usernames"].trim().split("\n").filter((value) => value.trim().length > 1) : []; const usernameRecipients = []; const phoneRecipients = []; for (let index = 0; index < recipients.length; index++) { if (recipients[index] !== "") { if (recipients[index].match(/[a-zA-Z@]+/g) !== null) { usernameRecipients.push(recipients[index]); } else { const normalized = recipients[index].replace(/ /g, "").replace(/-/g, ""); recipients[index] = normalized; } } } if (usernameRecipients.length > phoneRecipients.length) { sendLog(110003, { message: settings["message"], usernames: recipients.join(","), message_type: "username" }); chrome.tabs.sendMessage(activeTabId, { type: "message_by_username", data: recipients }); } else { sendLog(110003, { message: settings["message"], usernames: recipients.join(","), message_type: "phone" }); chrome.tabs.sendMessage(activeTabId, { type: "add_contact", data: recipients, index: 0 }); }}API invite validation starts the invite workflow
popup.jsfunction startApiInvite(skipTip) { if (this.version_low) { sendLog(110112, { error: "version too low" }); return; } if (this.limit["invite"] <= 0) { this.upgrade_content = this.plan_code === "free" ? this.invite_tip_1 : this.invite_tip_3; this.upgradeDialogVisible = true; sendLog(110112, { error: "limited" }); return; } if (this.login_status !== 1) { this.tip_content = this.api_scrape_tip_2; this.tipDialogVisible = true; sendLog(110112, { error: "unlogin" }); return; } if (!this.api_invite_group_link) { this.tip_content = this.invite_tip_6; this.tipDialogVisible = true; sendLog(110112, { error: "no group" }); return; } if (/\/t.me\//.test(this.api_invite_group_link) === false) { this.tip_content = this.invite_tip_8; this.tipDialogVisible = true; return; } const usernameText = this.invite_usernames.replace(/\n/g, ","); if (usernameText === "") { this.tip_content = this.invite_tip_2; this.tipDialogVisible = true; sendLog(110112, { error: "no usernames" }); return; } if (usernameText.split(",").length > this.limit["invite"]) { this.tip_content = this.invite_tip_4.replace("%", this.limit["invite"]); this.tipDialogVisible = true; return; } if (this.notShowInviteTip || skipTip) { const usernames = usernameText.split(","); this.api_invite_progress = true; this.before_tab = this.current_tab; this.current_tab = "progress-tab"; this.apiInvite(usernames, this.api_invite_group_link, 0); } else { this.inviteDialogVisible = true; }}API invite logs group_link and username_list
popup.jsasync function apiInvite(usernames, groupLink, startIndex) { sendLog(110112, { group_link: groupLink, username_list: usernames.join(",") }); await this.a({ progress: { current: startIndex, total: usernames.length } }); this.progress_interval = setInterval(this.n, 500); try { await this.client.connect(); } catch (error) { console.log(error); this.tip_content = this.connect_tip; this.tipDialogVisible = true; this.api_invite_progress = false; sendLog(110113, { group_link: groupLink, error: this.tip_content + error.message }); return; } let channel = groupLink; if (/t.me\/\+([\w-]{16})/.test(groupLink)) { const channelInput = await this.getChannelInput(groupLink.match(/t.me\/\+([\w-]{16})/)[1]); if (channelInput.error !== undefined) { this.current_tab = this.before_tab; sendLog(110113, { group_link: groupLink, error: channelInput.error }); return; } channel = channelInput.channelInput; } const result = { success: 0, total: usernames.length, await: usernames.length }; for (let index = startIndex; index < usernames.length && this.api_invite_progress; index++) { const delaySeconds = Math.max(Math.floor(Math.random() * (this.invites_wait_max - this.invites_wait_min) + this.invites_wait_min), 10); const username = usernames[index]; try { await this.client.connect(); if (channel.accessHash === undefined && channel.channelId !== undefined) { await this.client.invoke(new Api.messages.AddChatUser({ chatId: channel.channelId, userId: username })); } else { await this.client.invoke(new Api.channels.InviteToChannel({ channel, users: [username] })); } await this.client.disconnect(); await this.a({ progress: { current: index + 1, total: usernames.length } }); } catch (error) { console.log(error); await this.a({ progress: { current: index + 1, total: usernames.length } }); this.tip_content = error.message; sendLog(110113, { group_link: groupLink, error: this.tip_content + error.message }); await this.client.disconnect(); break; } result.success += 1; result.await -= 1; await this.a({ api_invite_result: result }); await this.wait(delaySeconds); } await this.a({ progress: { current: usernames.length, total: usernames.length } }); this.api_invite_progress = false; await this.a({ api_invite_result: null }); sendLog(110113, { group_link: groupLink, result }); this.setLimit("invite", result.success);}Group scraping logs each scraped profile URL
content-script.jsasync function scrapeGroup(groupLink, filter) { const eventType = 110007; if (groupLink && window.location.href !== groupLink) { groupLink += "?tgsendertype=1"; for (const key in filter) { groupLink += "&" + key + "=" + filter[key]; } window.open(groupLink, "_blank"); return; } const settings = await readStorage("settings"); const planCode = await readStorage("plan_code"); let limit = Number.isNaN(parseInt(settings["limit-group-scraper"])) ? 50 : parseInt(settings["limit-group-scraper"]); const lastSeen = filter["last_seen"]; const removeAdmin = filter["remove_admin"]; limit = planCode === "basic" || planCode === "pro" ? limit : Math.min(limit, maxFreeScrapeCount); await writeStorage({ progress: { current: 0, total: limit } }); const selectors = await getSelectors("scrape"); let groupId = ""; let groupMembers = ""; groupLink = window.location.href; let title = null; let titleAttempts = 0; while (titleAttempts < 15 && title == null) { await wait(1); title = document.querySelector(selectors["group_title"]); const broadcastButton = document.querySelector(selectors["channel_broadcast_button"]); if ((title && !title.getAttribute("data-peer-id").startsWith("-")) || broadcastButton != null) { title = null; break; } titleAttempts++; } if (title == null) { chrome.runtime.sendMessage({ message: "log", eventType, otherParams: { error: "group info not found", group_link: groupLink } }); chrome.runtime.sendMessage({ message: "complete", data: "scrape" }); running = false; return; } groupId = title.textContent; title.click(); await wait(1); const memberRows = [...document.querySelectorAll(selectors["group_list"])]; if (memberRows.length === 0) { chrome.runtime.sendMessage({ message: "showtip", data: "Group members not loaded, Please reload webpage." }); chrome.runtime.sendMessage({ message: "complete", data: "scrape" }); chrome.runtime.sendMessage({ message: "log", eventType, otherParams: { error: "group members not found", group_link: groupLink } }); running = false; return; } let peerIds = memberRows.map((row) => row.getAttribute("data-peer-id")); const groupLinks = [...document.querySelectorAll(selectors["group_link"])].filter((node) => /^http.*?$/.test(node.textContent)); if (groupLinks.length > 0) { groupId = groupLinks[groupLinks.length - 1].textContent; } const collectedUsernames = []; let scrapedCount = 0; for (let index = 0; running && index < peerIds.length && scrapedCount < limit; index++) { const userRecord = []; document.querySelector(selectors["group_title"]).click(); const row = document.querySelector(selectors["user_div"].replace("%user_id%", peerIds[index])); if (row == null) { window.location.href = groupLink; continue; } row.click(); await wait(1.5); const selfLink = document.querySelector(selectors["user_self"]); if (selfLink != null) { userRecord[0] = window.location.href; } const usernames = [...document.querySelectorAll(selectors["username"])].filter((node) => /^[a-zA-Z0-9_]{1,}$/.test(node.textContent)); userRecord[1] = usernames.length === 0 ? "" : "@" + usernames[0].textContent; const returnButton = document.querySelector(selectors["group_return"]); if (returnButton == null) { window.location.href = groupLink; continue; } returnButton.click(); const username = userRecord[1]; if (username !== undefined && username !== "bot" && username.length > 0 && username !== "Deleted Account" && !collectedUsernames.includes(username) && running) { scrapedCount++; collectedUsernames.push(username); settings["scraped-usernames"] = collectedUsernames.join("\n"); await writeStorage({ settings, progress: { current: scrapedCount, total: limit }, useLog: await shouldUseLog() }); } if (userRecord[0]) { chrome.runtime.sendMessage({ message: "log", eventType: 110005, otherParams: { user: userRecord[0] } }); } }}- extension-us.us-west-1.log.aliyuncs.com
Aliyun log service endpoint for the extension-us project and scrm-tg-sender logstore; receives workflow fields merged by the extension logger.