Is WASender Free Bulk Messaging Plugin safe?

Medium risk

Send bulk WhatsApp messages to multiple contacts without saving each number first.

This extension lets users add contacts manually or upload an Excel sheet, then type and send WhatsApp messages in bulk. The store description says Pro features add personalized messages, attachments, batching and time-gap controls, contact download, filtering, and message reports.

wasender-main-devsv1.0.77Chrome Web Store
45Risk

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

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

Account setup details are posted to TeleCRM

First-time setup collects your WhatsApp name, phone, email, country, and account phone into CRM lead records.

The source posts to a TeleCRM endpoint and an AWS Gateway endpoint, each with an embedded auth value.

No live POST was captured.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You submit the account setup form after entering your name, WhatsApp number, and email address.

The first-signup path runs after the extension saves those details.

The extension did this

The extension creates a CRM lead from those account details and prepares POST requests with embedded authorization values.

The source contains both a TeleCRM route and an AWS API Gateway CRM route for lead creation.

02EvidenceFIELD TABLE
Fields the source maps into CRM lead records
FieldValueWhy it matters
Your account name
Aman GuptaThis identifies the person or business account using the extension.
Your signup phone number
+91 8873520027This is the WhatsApp number you enter during setup, including country information.
Your email address
aman.gupta@example.inThis can identify and contact you outside the extension.
Your country selection
India / IN / +91This adds location context to the account record sent during setup.
Your extension account phone
+91 9027172291This links the CRM lead to the phone number already associated with the extension account.
Embedded CRM authorization
Bearer <redacted>The request includes a bundled authorization value rather than prompting you to approve a separate CRM connection.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://next-api.telecrm.in/enterprise/60a51c74589084cee99c1903/autoupdatelead
No live response was captured; the POST URL and method are reconstructed from the shipped service-worker source.
Headers
Content-Typeapplication/json
AuthorizationBearer <redacted>
04EvidenceNETWORK CAPTURE
Captured request
POSThttps://6vokqgdnu4.execute-api.ap-south-1.amazonaws.com/enterprise/67585858ed44a90bb3662ee1/entities
No live response was captured; the POST URL and method are reconstructed from the shipped main bundle source.
Headers
Content-Typeapplication/json
AuthorizationAdminBearer <redacted>
05EvidenceCODE COMPARE
The code that does this

The first-signup path and CRM POST helpers

What it actually does
First-time signup calls lead creationdeobfuscated/969.js
handleFirstTimeSignup() {
  var e = this;
  return (0, l.Z)(function*() {
    try {
      yield e.log.getCookies(), e.log.createLead({
        fields: e.log.getCookies(),
        actiontexts: ["Signup done", JSON.stringify(e.log.getCookies(), null, 2)]
      })
    } catch (o) {
      console.error("Error in creating lead", o)
    }
    try {
      e.extDataService.getAuth()?.isPro() || (yield e.userService.activateFreePlan(), setTimeout(() => {
        e.uiService.refresh()
      }, 0))
    } catch (o) {
      console.error("Error in activating free plan", o)
    }
  })()
}
Log wrapper copies extension account details into leadService.createLeaddeobfuscated/main.js
createLead(pt) {
  var it = this;
  return (0, h.Z)(function*() {
    const lt = it.extAuth.currentUser.phoneNumber,
      It = it.extAuth.currentUser.name,
      Ut = it.extAuth.getUserDetails()?.email,
      Ot = it.extAuth.getUserDetails()?.countryInfo,
      xn = it.extAuth.getUserDetails()?.number;
    return it.leadService.createLead({
      name: It,
      phoneNumber: xn,
      email: Ut,
      countryInfo: Ot,
      alternatePhone: lt
    })
  })()
}
Lead service builds the CRM entity and POSTs to AWS API Gatewaydeobfuscated/main.js
createLead(pt) {
  return (0, h.Z)(function*() {
    const it = [{
      fieldId: "contact_name",
      type: "TEXT",
      valueText: pt.name
    }, {
      fieldId: "contact_phone",
      type: "PHONE",
      valuePhone: {
        countryInfo: pt.countryInfo || {
          name: "India",
          code: "IN",
          dial_code: "91"
        },
        phoneNumber: pt.phoneNumber
      }
    }, {
      fieldId: "contact_status",
      type: "DROPDOWN",
      valueDropdown: "New lead"
    }, {
      fieldId: "contact_source",
      type: "DROPDOWN",
      valueDropdown: "WACA"
    }];
    pt.email && it.push({
      fieldId: "contact_email",
      type: "EMAIL",
      valueEmail: pt.email
    }), pt.alternatePhone && it.push({
      fieldId: "1_TEXT",
      type: "TEXT",
      valueText: pt.alternatePhone
    });
    const lt = {
      id: "",
      eId: H,
      entityType: "CONTACT",
      fields: it,
      parentEntityId: "",
      parentEntityType: null,
      activities: [],
      createdBy: "",
      lastSeenTimestamp: 0,
      creationTimestamp: Date.now(),
      modificationTimestamp: Date.now()
    };
    try {
      const It = yield fetch(`https://6vokqgdnu4.execute-api.ap-south-1.amazonaws.com/enterprise/${H}/entities`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: "AdminBearer <redacted>"
        },
        body: JSON.stringify({
          entity: lt
        })
      });
      if (!It.ok) {
        const Ot = yield It.json();
        throw new Error(`HTTP ${It.status}: ${Ot.message||"Unknown error"}`)
      }
      return yield It.json()
    } catch (It) {
      console.error("Error creating contact:", It)
    }
  })()
}
Service worker POSTs lead properties to TeleCRMdeobfuscated/service-worker.js
static createLead(e) {
  var a = this;
  return o(function*() {
    return a.httpPost(`${y_CRM_ROUTE}`, e, {
      Authorization: "Bearer <redacted>"
    })
  })()
}
Message route exposes the service-worker lead creation calldeobfuscated/service-worker.js
case t.API_CREATE_LEAD:
  return g.createLead(i.data.properties).then(n => a(n)).catch(n => {
    console.error("Error in API_CREATE_LEAD:", n), a(null)
  }), !0
06EvidenceTHIRD PARTY LIST
External CRM destinations referenced by the shipped code
  • next-api.telecrm.in

    TeleCRM endpoint used by the service worker to receive lead properties from the extension.

  • 6vokqgdnu4.execute-api.ap-south-1.amazonaws.com

    AWS API Gateway endpoint used by the main bundle to create CRM contact entities.

SeverityLOW
ClassUNWANTED
TypeUnexpected
CWECWE-522
SourceAI SANDBOX

Configured campaign cookies are added to analytics events

After an authenticated session on WhatsApp Web, the extension reads wasender.com cookies (origin, clickid, campaignid) as Amplitude properties sent to its cloud endpoint.

Logged-out POSTs show empty properties; authentication was untested.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open the extension while a WhatsApp Web session is available.

The cookie collection path runs during the extension refresh after it loads account and app configuration data.

The extension did this

The extension reads configured wasender.com cookie names and stores any returned values as analytics properties.

Later Amplitude events include those properties in the userProperties object sent to the cloud function.

02EvidenceFIELD TABLE
Cookie fields configured for analytics properties
FieldValueWhy it matters
Campaign origin cookie
origin=google (illustrative)This can connect your extension activity to a campaign or referral source if the cookie is present.
Click attribution cookie
clickid=CjwKCAjwq7fABhB2EiwAwk-Yb9mE2 (illustrative)This can tie your extension session to a specific advertising or referral click if the cookie is present.
Campaign ID cookie
campaignid=summer_launch_2026 (illustrative)This can add campaign context to analytics events associated with your extension session.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://us-central1-waplugin-34798.cloudfunctions.net/stats/log-amplitude
Dynamic analysis observed logged-out analytics posts with empty userProperties.
Body
{eventType:logged_out,userId:null,userProperties:{}}
04EvidenceCODE COMPARE
The code that does this

Cookie values are merged into Amplitude userProperties

What it actually does
Default and remotely configurable cookie namesmain.js
constructor(b, H, ee, De, be, $e = Q.BUY_FRAME_DEFAULT_URL, Xe = !1, Ht = 10, pt, it, lt) {
  this.DEFAULT_COOKIES = ["origin", "clickid", "campaignid"], this.reviewMode = !1, this.freeMessageLimit = 10, this.waSenderCookies = this.DEFAULT_COOKIES, this.suggestBan = b, this.banner = H, this.bannerIframeLink = ee, this.helpNumber = De, this.buyFrameLink = $e, this.reviewMode = Xe, this.freeMessageLimit = Ht, this.waSenderCookies = pt, this.autoRenewalLink = it, this.manageRenewalLink = lt, this.onBoardingFrameURL = be
}
static fromJSON(b) {
  let H = null;
  return b && (H = new Q({}, {}, "", "", "", Q.BUY_FRAME_DEFAULT_URL, !1, 10, [], "", ""), H.suggestBan = b.suggestBan ? b.suggestBan : {}, H.banner = b.banner ? b.banner : {}, H.bannerIframeLink = b.bannerIframeLink ? b.bannerIframeLink : "", H.helpNumber = b.helpNumber ? b.helpNumber : "", H.buyFrameLink = b.buyFrameLink ? b.buyFrameLink : Q.BUY_FRAME_DEFAULT_URL, H.reviewMode = !!b.reviewMode && b.reviewMode, H.freeMessageLimit = b.freeMessageLimit ? b.freeMessageLimit : 10, H.waSenderCookies = b.waSenderCookies ? b.waSenderCookies : H.DEFAULT_COOKIES, H.autoRenewalLink = b.autoRenewalLink ? b.autoRenewalLink : "", H.manageRenewalLink = b.manageRenewalLink ? b.manageRenewalLink : "", H.onBoardingFrameURL = b.onBoardingFrameURL ? b.onBoardingFrameURL : ""), H
}
Cookie source URL constantmain.js
const q = {
  APP_SERVER_ROUTER: "https://wasender.ai/api/b1",
  APP_SERVER: "http://wasender.ai/api",
  CLIENT_URL: "https://wasender.com",
  SAFE_AND_MONTERING: "6558718ad99bc6ef5c706a94",
  NEW_CUSTOMER: "6284dde8d771c4000970f6bc",
  TEMPLATE_ID_SAFE_AND_MONTERING: "6558718ad99bc6ef5c706a97",
  TEMPLATE_ID_NEW_CUSTOMER: "64c2a9c678aa832f68e69a2f",
  BAN_PREVENT_LINK: "https://wasender.ai/banning-tips-ai?send=true",
  ROOT_LAMBDA_URL: "http://localhost:3000",
  ROOT_URL: "https://us-central1-waplugin-34798.cloudfunctions.net",
  DATA_S3: "https://wasender-data.s3.ap-south-1.amazonaws.com",
  INDEX_DB_NAME: "wanew",
  CRM_ROUTE: "https://next-api.telecrm.in/enterprise/60a51c74589084cee99c1903/autoupdatelead",
  ROOT_LAMBDA_URL: "https://3fsfla67nmbaykipymj7qlsahy0uymzo.lambda-url.ap-south-1.on.aws",
  SELECTOR_URL: "https://us-central1-waplugin-34798.cloudfunctions.net/stats/adds",
  SUPPORT_NUMBER: "919528066739",
  chatSyncBatchSize: 50
}
Chrome cookie read helpermain.js
getCookie(be, $e) {
  return new Promise((Xe, Ht) => {
    chrome.cookies.get({
      url: be,
      name: $e
    }, pt => {
      chrome.runtime.lastError ? (console.error(chrome.runtime.lastError), Ht(chrome.runtime.lastError)) : Xe({
        value: pt?.value,
        name: $e
      })
    })
  })
}
Configured cookies copied into analytics propertiesmain.js
getWaSenderCookie() {
  var x = this;
  return (0, Q.Z)(function*() {
    try {
      const de = wt.N.CLIENT_URL,
        We = x.extDataService.appConfig.waSenderCookies.map(Kt => x.waService.getCookie(de, Kt)),
        Ke = yield Promise.all(We), ft = {};
      Ke.forEach(Kt => {
        Kt?.value && (ft[Kt.name] = Kt.value)
      }), x.log.setUniversalUserProperties(ft)
    } catch (K) {
      console.error("Error in getting cookies", K)
    }
  })()
}
Universal properties included in log payloadmain.js
setUniversalUserProperties(pt) {
  Xe.UNIVERSAL_USER_PROPERTIES = {
    ...Xe.UNIVERSAL_USER_PROPERTIES,
    ...pt
  }
}
log(pt, it = {}, lt = {}) {
  var It = this;
  return (0, h.Z)(function*() {
    const Ut = It.extAuth.currentUser.phoneNumber,
      Ot = k.nB.getManifestData(),
      wt = {
        ...Object.fromEntries(Object.entries(it).map(([Mt, Rt]) => [Mt, String(Rt)])),
        version: String(Ot?.version),
        isPro: String(It.extAuth.isPro())
      },
      Nt = {
        extUserName: String(It.extAuth.currentUser.name),
        extPhoneNumber: String(It.extAuth.currentUser.phoneNumber),
        phoneNumber: It.extAuth.getUserDetails()?.number,
        signupNumber: `${It.extAuth.getUserDetails()?.countryInfo?.dial_code}${It.extAuth.getUserDetails()?.number}`,
        email: It.extAuth.getUserDetails()?.email,
        name: It.extAuth.getUserDetails()?.name,
        countryCode: It.extAuth.getUserDetails()?.countryInfo?.name,
        getTips: String(It.extAuth.getUserDetails()?.getTips),
        installTimestamp: String(It.extAuth.getUserDetails()?.installTimestamp),
        verifiedEmail: String(It.extAuth.getUserDetails()?.verifiedEmail),
        isPro: String(It.extAuth.isPro()),
        ...Xe.UNIVERSAL_USER_PROPERTIES
      };
    yield k.bl.logAmplitude(pt, Ut, wt, Nt)
  })()
}
Service worker POST to cloud functionservice-worker.js
static logAmplitude(e, a, r, n) {
  var E = this;
  return o(function*() {
    const m = `${y_ROOT_URL}/stats/log-amplitude`;
    yield E.httpPost(m, {
      eventType: e,
      userId: a,
      eventProperties: r,
      userProperties: n
    }, {
      Authorization: `Bearer ${a}`,
      Version: E.getVersion()
    })
  })()
}
05EvidenceTHIRD PARTY LIST
Analytics destination
  • us-central1-waplugin-34798.cloudfunctions.net

    Receives Amplitude event payloads from the extension, including the userProperties object populated by the cookie-reading code path.

Updated 17 September 2026heogilejknffekkbjdjmoamaehdblmnc