Is WA Bulk Message Sender safe?

Medium risk

WA Bulk Message Sender is medium risk. Once linked, the extension reads your WhatsApp number from storage and sends it in analytics to Alibaba, with user agent, install time, and a persistent UUID. A marker login appeared in s_user_phone_num, plus a derived country code.…

bulk-senderv1.3.23Chrome Web Store
52Risk
Who publishes it

bulk-sender - no other listings under this identity, 5 shared hostnames

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

Publisher
bulk-sender

Shared hosts - 5 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.

admaker.ai
Also called by 1 other listing: Ads Library Downloader & Ad Finder & Adspy Tool - Save Facebook,TikTok Ads
sender.watechdev.asia
Also called by 1 other listing: WAWebSender - Bulk Group Messaging & Member Management
zhuyuntao.cn
Also called by 2 other listings: WA Self Sender & Easy Sender - Personalized WA Messages & Group Tools, WARocket Sender:Rocket Sender - WAPI Sender
lab.zingfront.com
Also called by 4 other listings, including THunt AI Review Analysis & Download, Tab Manager - Smart Tab By NoteGPT AI
cdn.waplus.io
Also called by 5 other listings, including WAWebSender - Bulk Group Messaging & Member Management, WA Self Sender & Easy Sender - Personalized WA Messages & Group Tools

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

Publishers can request a review.

Findings

WhatsApp Phone Number Sent to Alibaba Cloud Logging Endpoint

Once linked, the extension reads your WhatsApp number from storage and sends it in analytics to Alibaba, with user agent, install time, and a persistent UUID.

A marker login appeared in s_user_phone_num, plus a derived country code.

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

You link a WhatsApp account to the extension and continue using it normally.

The extension's own login detection picks up the phone number tied to your WhatsApp Web session and stores it locally.

The extension did this

The next time the extension logs any analytics event, your phone number is included in a payload sent to an Alibaba Cloud logging endpoint.

The request also carries a country code derived from the phone number, your browser's user agent, install time, and a persistent install UUID.

Fields in the Alibaba Cloud log payload
  • WhatsApp phone number
    14155552671

    The phone number linked to your WhatsApp Web session, sent as plain text.

  • Country code
    US

    A country code the extension derives from your phone number using a phone-number-parsing library.

  • Install UUID
    3f1b2e7a-9c4d-4a2b-8e31-6a2f9d0c1234

    A persistent identifier for your install lets Alibaba Cloud tie every event back to the same browser over time.

  • User agent
    Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36

    Your browser and OS details, sent with every event.

  • Install time
    1756540700

    When the extension was first installed on this browser.

Captured request
POSThttps://extension-us.us-west-1.log.aliyuncs.com/logstores/scrm-web-sender-juzhen/track

200 OK. Observed during dynamic analysis: a planted marker value written to chrome.storage.local's userPhoneNum key was reflected verbatim in this request's s_user_phone_num field.

Headers
Content-Type
application/x-protobuf
x-log-apiversion
0.6.0
Body
{  "__logs__": [    {      "event_source": "9",      "event_type": "900008",      "event_time": "1756540800",      "install_time": "1756540700",      "platform": "win32",      "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",      "uuid": "3f1b2e7a-9c4d-4a2b-8e31-6a2f9d0c1234",      "version": "1.3.12",      "language": "en-US",      "s_user_phone_num": "14155552671",      "s_country_code": "US"    }  ]}
The code that does this

Log-payload builder, shipped vs. deobfuscated

Readable version

dealLog / function b -- reads storage, builds payload, sends

background.js:3554-3617
function b(t) {  if (a) return;  let e = t.eventType,    n = t.otherParams;  try {    chrome.storage.local.get(["browserInfo", "platform", "installTime", "userPhoneNum", "firstSendTime", "uuid", "version", "language"], (function(t) {      if (!a) {        a = !0;        try {          let d = void 0 !== t.browserInfo ? t.browserInfo : "",            i = d ? d.userAgent : "",            s = d ? d.browser : "",            l = void 0 !== t.platform ? t.platform : "",            c = void 0 !== t.installTime ? t.installTime : "",            h = t.userPhoneNum,            f = void 0 !== t.firstSendTime ? t.firstSendTime : "",            $ = void 0 !== t.uuid ? t.uuid : "",            p = void 0 !== t.version ? t.version : "",            m = void 0 !== t.language ? t.language : "",            y = chrome.runtime.getManifest().version,            b = void 0 === e ? null : o["a"][e],            v = {              event_source: 9,              event_type: e,              event_type_desc: b,              event_time: Math.round(new Date / 1e3),              install_time: c,              s_first_send_time: f,              platform: l,              user_agent: i,              browser_type: s,              uuid: $,              version: p,              version_current: y,              language: m            };          if (void 0 !== h) {            let t = r("a689"),              e = t("+" + h).getRegionCode(),              n = new t(h.toString(), e),              d = n.getRegionCode() ? n.getRegionCode() : "",              i = A(d) ? A(d) : "";            v.s_user_phone_num = h, v.s_country_code = i, chrome.storage.local.set({              phoneNum: h,              countryCode: i            })          } else v.s_user_phone_num = "", v.s_country_code = "";          if (u = {              event_source: v.event_source,              install_time: v.install_time,              s_first_send_time: v.s_first_send_time,              platform: v.platform,              user_agent: v.user_agent,              browser_type: v.browser_type,              uuid: v.uuid,              version: v.version,              version_current: v.version_current,              language: v.language,              s_user_phone_num: v.s_user_phone_num,              s_country_code: v.s_country_code            }, n)            for (let t in n) v[t] = n[t];          g.send(v)        } catch (d) {          c("[log-util] dealLog failed", d)        } finally {          a = !1        }      }    }))  } catch (d) {    a = !1, c("[log-util] dealLog failed", d)  }}
Destination for the phone number
    • extension-us.us-west-1.log.aliyuncs.com

    Receives the phone number, derived country code, user agent, and install UUID via Alibaba SLS project 'extension-us', logstore 'scrm-web-sender-juzhen', on every logged event.

Uninstall URL includes the WhatsApp number

After WhatsApp Web loads, the extension reads and saves the phone number.

The background script sets Chrome's uninstall URL for wawebsender.com with that number in utm_source, plus install time, country, UUID.

No navigation captured.

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

You use WhatsApp Web while the extension is active.

The content script runs after the page loads and reads the WhatsApp account identifier from browser storage.

The extension did this

The extension prepares an uninstall URL that includes your WhatsApp number.

Chrome opens that URL if the extension is removed after the number has been stored.

Fields placed in or associated with the uninstall URL
  • WhatsApp number
    +15551234567 (illustrative)

    This directly ties the uninstall event to your WhatsApp account number.

  • Browser profile UUID
    8f01c3d9-4bc0-42e4-8d49-e4d3d820a315

    This can link the uninstall event back to the same browser profile across extension events.

  • Install time
    1719849600000

    This records when the extension was first installed on the browser profile.

  • Country value
    United States (illustrative)

    This adds regional context derived from the phone number.

  • Extension version
    1.3.0

    This identifies which installed version generated the uninstall URL.

  • Connection IP address
    198.51.100.24 (illustrative)

    The receiving server can associate the uninstall request with the network connection that made it.

Captured request
GEThttps://wawebsender.com/waplus-sender-uninstall/?utm_source=+15551234567&install_time=1719849600000&country_name=United%20States&uuid=8f01c3d9-4bc0-42e4-8d49-e4d3d820a315&version=1.3.0&version_current=1.3.0&extension_name=WA%20Bulk%20Message%20Sender

No request body is used; this is the URL shape the extension registers for Chrome to open on uninstall.

The code that does this

The content script collects the number, then the background script writes it into the uninstall URL

Readable version

Content script extracts `last-wid` and sends `userPhoneNum`

deobfuscated/js/inject/content-script.js:86751-86764
function Ht() {  try {    let e, t = window.localStorage.getItem("extensionCrm");    t && chrome.storage.local.set({      extensionCrm: !0    }).then(e => {}), window.localStorage.getItem("last-wid") ? (e = window.localStorage.getItem("last-wid"), ht = e.split("@")[0].substring(1)) : (e = window.localStorage.getItem("last-wid-md"), ht = e.split(":")[0].substring(1))  } catch (e) {}  return chrome.storage.local.set({    userPhoneNum: ht,    loadTimes: 1  }).then(e => {}), b(Y["e"], {    userPhoneNum: ht  }, "background"), ht}

Background handler builds the uninstall URL with `utm_source`

deobfuscated/js/background.js:14527-14543
_(R["e"], async ({  data: t}) => {  let e = r("a689"),    d = e("+" + t.userPhoneNum).getRegionCode(),    n = new e(t.userPhoneNum.toString(), d),    i = n.getRegionCode() ? n.getRegionCode() : "",    o = Object(E["c"])(i) ? Object(E["c"])(i) : "",    a = t.userPhoneNum;  chrome.storage.local.get(["installTime", "uuid", "version"], (function(t) {    let e = void 0 !== t.installTime ? t.installTime : "",      r = void 0 !== t.uuid ? t.uuid : "",      d = void 0 !== t.version ? t.version : "",      n = chrome.runtime.getManifest().version,      i = "https://wawebsender.com/waplus-sender-uninstall/?utm_source=" + a + "&install_time=" + e + "&country_name=" + o + "&uuid=" + r + "&version=" + d + "&version_current=" + n + "&extension_name=" + s["a"];    s["c"] || chrome.runtime.setUninstallURL(i)  }))})
Destination receiving the uninstall URL
    • wawebsender.com

    Receives the uninstall URL containing the phone-number query parameter and install metadata.

AI rewrite sends WhatsApp draft text to remote services

Enabling AI Rewrite sends the draft text and rewrite count to wawebsender.com/api/ai/rewrite, with your stored WhatsApp number in a header, and logs the call to Alibaba Cloud as event 901402.

Not captured live; per shipped code.

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

You ask the extension to rewrite a WhatsApp message with AI.

The UI exposes this as `AI Rewrite` and says it generates personalized copies after enabling.

The extension did this

The extension sends the draft message text to a rewrite API and records the API input and output in its log pipeline.

The logging code includes the request object, the response object, timing fields, and stored user identifiers.

Fields handled by the AI rewrite and logging path
  • WhatsApp draft text
    Can we move the meeting to 3 PM? (illustrative)

    This is the message content you are preparing before sending it in WhatsApp.

  • Rewrite count
    25

    This tells the service how many rewritten message variants to generate.

  • WhatsApp number
    +15551234567 (illustrative)

    This ties the rewrite request to your WhatsApp account number.

  • Rewrite response
    Could we shift our meeting to 3 PM? (illustrative)

    The rewritten message variants can also be passed into the extension's logging path.

  • API URL in log
    https://wawebsender.com/api/ai/rewrite

    This identifies which remote API call the logged input and output came from.

Captured request
POSThttps://wawebsender.com/api/ai/rewrite

The code expects a JSON response whose `data.rewrites` array is displayed in the AI Rewrite preview.

Headers
Content-Type
application/json
Whatsapp-Number
+15551234567 (illustrative)
Captured request
POSThttps://extension-us.us-west-1.log.aliyuncs.com/logstores/scrm-web-sender-juzhen/track

The bundled log client posts queued records to Alibaba Cloud Simple Log Service.

Headers
Content-Type
application/x-protobuf
x-log-apiversion
0.6.0
The code that does this

AI Rewrite UI, rewrite request, API log event, and Alibaba Cloud transport

Readable version

AI Rewrite switch triggers generation

deobfuscated/js/popup.js:3930-3934
aiRewriteEnabled: {  immediate: !1,  handler(e) {    e ? this.aiRewriteResults && 0 !== this.aiRewriteResults.length || this.handleAiRewriteGenerate(1) : (this.aiRewriteLoading = !1, this.aiRewriteError = !1)  }}

The generation method reads draft text and calls the rewrite helper

deobfuscated/js/popup.js:3944-3995
async handleAiRewriteGenerate(e = 1) {  if (!this.aiRewriteLoading) {    this.$sendLog(909100, {      c_message: "AI重写开始生成",      c_type: e    }), this.aiRewriteLoading = !0, this.aiRewritePreviewExpanded = !1, this.aiRewriteResults = [], this.aiRewriteEditingId = null, this.aiRewriteEditingDraft = "", this.aiRewriteError = !1;    try {      var t, a, o;      const e = String(null !== (t = this.messageText) && void 0 !== t ? t : "").trim(),        i = null !== (a = this.aiRewriteTotal) && void 0 !== a ? a : 0,        n = Number(i);      if (!e) return this.aiRewriteEnabled = !1, chrome.storage.local.set({        aiRewriteEnabled: !1      }), this.$message({        type: "warning",        message: this.$t("ai_rewrite_validate_message_empty_t")      }), void this.$sendLog(909101, {        c_type: 1,        c_message: "输入内容为空"      });      if (!Number.isFinite(n) || n <= 0) return this.aiRewriteEnabled = !1, chrome.storage.local.set({        aiRewriteEnabled: !1      }), this.$message({        type: "warning",        message: this.$t("ai_rewrite_validate_total_invalid_t")      }), void this.$sendLog(909101, {        c_type: 1,        c_message: "发送的数量为空"      });      let r = await ne(e, n, "gpt-4o"),        l = (null === r || void 0 === r || null === (o = r.data) || void 0 === o ? void 0 : o.rewrites) || [];      if (!Array.isArray(l) || 0 === l.length) throw new Error("AI 返回内容为空");      if (Array.isArray(l) && l.length > 0 && l.length < n) {        const e = [];        for (let t = 0; t < n; t++) e.push(l[t % l.length]);        l = e      }      this.aiRewriteResults = (l || []).slice(0, n).map((e, t) => ({        id: t + 1,        text: String(e || "")      }));      try {        chrome.storage.local.set({          aiRewriteEnabled: this.aiRewriteEnabled,          aiRewriteResults: this.aiRewriteResults        })      } catch (s) {}    } catch (i) {      this.aiRewriteError = !0    } finally {      this.aiRewriteLoading = !1    }  }}

Rewrite helper reads the stored WhatsApp number

deobfuscated/js/popup.js:3720-3739
const ne = async (e, t = 300, a = "gpt-4o") => {  const o = await chrome.storage.local.get(["whatsappNumber"]),    s = Date.now(),    i = await Object(ie["a"])(e, t, o.whatsappNumber),    n = Date.now() - s;  if (!i) throw Object(F["c"])(909101, {    c_type: 3,    c_message: "无返回数据",    c_duration_ms: n  }), new Error("AI 返回内容为空");  if (1e5 !== i.code && "100000" !== i.code) throw Object(F["c"])(909101, {    c_type: 3,    c_message: JSON.stringify(i),    c_duration_ms: n  }), new Error(i.message);  return Object(F["c"])(909101, {    c_type: 2,    c_message: JSON.stringify(i),    c_duration_ms: n  }), i};

Rewrite POST sends draft text and count, with WhatsApp number in a header

deobfuscated/js/popup.js:10683-10717
async function g(e, t, a) {  const n = Date.now(),    r = o["a"] + "api/ai/rewrite",    l = {      text: e,      maxNum: t,      whatsappNumber: a    };  try {    const o = await fetch(r, {      method: "POST",      headers: {        "Content-Type": "application/json",        "Whatsapp-Number": a      },      body: JSON.stringify({        text: e,        maxNum: t      })    });    200 !== o.status && Object(s["a"])({      eventType: 900014,      otherParams: {        url: o.url,        status: o.status      }    });    const c = await o.json();    return Object(i["a"])(r, l, c, n, !0), c  } catch (c) {    throw Object(i["a"])(r, l, {      error: (null === c || void 0 === c ? void 0 : c.message) || String(c)    }, n, !1), c  }}

API logger records URL, input, output, and duration as event 901402

deobfuscated/js/popup.js:14584-14609
const s = 901402;function i(e) {  try {    if (void 0 === e) return "";    const t = "string" === typeof e ? e : JSON.stringify(e);    return t.length > 12e3 ? t.slice(0, 12e3) + "...[truncated]" : t  } catch (t) {    return String(e)  }}function n(e, t, a, n, r) {  const l = Date.now() - n;  Object(o["a"])({    eventType: s,    otherParams: {      c_type: r ? 1 : 2,      s_api_url: e,      s_api_input: i(t),      s_api_output: i(a),      s_request_time_ms: n,      s_request_duration_ms: l    }  })}

Log transport posts queued records to Alibaba Cloud SLS

deobfuscated/js/inject/content-script.js:17231-17264
logger: async function(e = !1) {  var t = !1 !== e && e.length > 0 ? e : [];  if (0 !== t.length) {    var n = "https://" + this.project + "." + this.host + "/logstores/" + this.logstore + "/track";    try {      var r = JSON.stringify({        __logs__: t      });      let e = new Headers({        "x-log-apiversion": "0.6.0",        "x-log-bodyrawsize": r.length      });      if (this.compress) {        e.append("x-log-compresstype", "lz4");        var i = c.from(r),          o = d.encodeBound(r.length),          a = c.alloc(o),          s = d.encodeBlock(i, a);        a = a.slice(0, s), fetch(n, {          method: "POST",          headers: e,          body: a        }).then(e => e)      } else {        let t = new Blob([r], {          type: "application/x-protobuf"        });        e.append("Content-Type", "application/x-protobuf"), fetch(n, {          method: "POST",          headers: e,          body: t        }).then(e => e)      }    } catch (l) {}  }}
Remote services used by the rewrite and logging path
    • wawebsender.com

    Receives the AI Rewrite API request containing the draft text and rewrite count.

    • us-west-1.log.aliyuncs.com

    Alibaba Cloud Simple Log Service endpoint that receives queued extension log records, including the rewrite API input and output fields.

+2 more findings not shown

Updated 30 September 2026fhkimgpddcmnleeaicdjggpedegolbkb