Is B Tools safe?

Medium risk

B Tools sends the logged-in WhatsApp user's name, phone number, email and country as a sales lead to two undisclosed third-party CRMs.

When you're signed in to WhatsApp, B Tools reads your name, phone number, email, country, and an alternate phone number, then posts them as a new sales lead to two separate CRM backends: TeleCRM and a CRM on Joyz.ai's platform. The same identity fields are also attached to every analytics event the extension sends to its vendor. One of the CRM endpoints is called using a static admin-level credential that is shipped in the extension's code and identical for every install. None of this data collection is mentioned in the extension's store listing, which describes only local WhatsApp message-template features.

arnavsingh9971v4.9.1.95Chrome Web Store
45Risk

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI FOUND

B Tools posts the logged-in WhatsApp user's identity to two undisclosed CRMs

Code analysis shows B Tools reads the logged-in WhatsApp account's name and phone number, plus an email, country and alternate phone from its own signup, then posts it as a sales lead to two undisclosed CRMs using hardcoded tokens.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open the B Tools popup on web.whatsapp.com while logged in to WhatsApp and complete the extension's own account setup screen.

Entering an email or a second phone number during that setup populates the extension's own account record.

The extension did this

The extension reads your WhatsApp name and phone number plus your account email, country and alternate phone, then posts them as a new sales lead to two undisclosed CRM backends.

The same fields are also attached as user properties on every analytics event the extension sends to its vendor's Amplitude pipeline.

02EvidenceFIELD TABLE
Fields packaged into the sales-lead record
FieldValueWhy it matters
Your name
Priya SharmaSent to two CRM backends as the lead's contact name, so anyone with CRM access can see who you are.
Your account phone number
+919876543210Sent as the lead record's phone number field to both CRM backends.
Your email address
priya.sharma@example.comSent to both CRMs as your contact email, and attached to every analytics event.
Your country
IndiaSent as context on the lead record; also used to pick a default dial code.
Your logged-in WhatsApp number
+919812345678Your actual logged-in WhatsApp number is sent too, labelled as an alternate phone.
03EvidenceCODE COMPARE
The code that does this

Building and posting the sales lead to both CRMs

What it actually does
Assembling the lead from your WhatsApp/account datamain.js
class UniversalUserPropertiesService {
  // The DI-injected argument is accepted but never used
  createLead() {
    const self = this;
    return (async function* () {
      // NOTE: 'alternatePhone' below is actually the logged-in WhatsApp
      // account's own phone number (extAuth.currentUser), not a second
      // number the user entered.
      const waAccountPhoneNumber = self.extAuth.currentUser.phoneNumber;
      const waAccountName = self.extAuth.currentUser.name;
      const signupEmail = self.extAuth.getUserDetails()?.email;
      const signupCountryInfo = self.extAuth.getUserDetails()?.countryInfo;
      const signupPhoneNumber = self.extAuth.getUserDetails()?.number;

      return self.leadService.createLead({
        name: waAccountName,
        phoneNumber: signupPhoneNumber,
        email: signupEmail,
        countryInfo: signupCountryInfo,
        alternatePhone: waAccountPhoneNumber,
      });
    })();
  }
}
Posting the lead to Joyz.ai's CRM with a hardcoded admin tokenmain.js
const JOYZ_ENTERPRISE_ID = "67585858ed44a90bb3662ee1";

class JoyzLeadService {
  async createLead(lead) {
    const fields = [
      { fieldId: "contact_name", type: "TEXT", valueText: lead.name },
      {
        fieldId: "contact_phone",
        type: "PHONE",
        valuePhone: {
          countryInfo: lead.countryInfo || {
            name: "India",
            code: "IN",
            dial_code: "91",
          },
          phoneNumber: lead.phoneNumber,
        },
      },
      { fieldId: "contact_status", type: "DROPDOWN", valueDropdown: "New lead" },
      { fieldId: "contact_source", type: "DROPDOWN", valueDropdown: "WACA" },
    ];

    if (lead.email) {
      fields.push({ fieldId: "contact_email", type: "EMAIL", valueEmail: lead.email });
    }
    if (lead.alternatePhone) {
      fields.push({ fieldId: "1_TEXT", type: "TEXT", valueText: lead.alternatePhone });
    }

    const entity = {
      id: "",
      eId: JOYZ_ENTERPRISE_ID,
      entityType: "CONTACT",
      fields,
      parentEntityId: "",
      parentEntityType: null,
      activities: [],
      createdBy: "",
      lastSeenTimestamp: 0,
      creationTimestamp: Date.now(),
      modificationTimestamp: Date.now(),
    };

    try {
      const res = await fetch(
        `https://6vokqgdnu4.execute-api.ap-south-1.amazonaws.com/enterprise/${JOYZ_ENTERPRISE_ID}/entities`,
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            // Hardcoded admin-scoped credential, identical for every install
            Authorization: "<redacted>",
          },
          body: JSON.stringify({ entity }),
        },
      );
      if (!res.ok) {
        const err = await res.json();
        throw new Error(`HTTP ${res.status}: ${err.message || "Unknown error"}`);
      }
      return await res.json();
    } catch (err) {
      console.error("Error creating contact:", err);
    }
  }
}
TeleCRM's hardcoded lead-intake endpointservice-worker.js
// Hardcoded TeleCRM lead-intake endpoint, same enterprise ID for every install
const TELECRM_ROOT_URL = "https://us-central1-waplugin-34798.cloudfunctions.net",
  TELECRM_LEAD_ROUTE =
    "https://next-api.telecrm.in/enterprise/60a51c74589084cee99c1903/autoupdatelead",
Posting to TeleCRM with a hardcoded bearer tokenservice-worker.js
class BackgroundApi {
  static createLead(leadPayload) {
    const self = this;
    return (async function* () {
      return self.httpPost(`${TELECRM_LEAD_ROUTE}`, leadPayload, {
        // Hardcoded bearer token, identical for every install
        Authorization: "Bearer <redacted>",
      });
    })();
  }
}
Popup relays the lead request to the background service workerservice-worker.js
case Messages.API_CREATE_LEAD:
  return (
    BackgroundApi
      .createLead(message.data.properties)
      .then((result) => sendResponse(result))
      .catch((err) => {
        console.error("Error in API_CREATE_LEAD:", err);
        sendResponse(null);
      }),
    true
  );
04EvidenceTHIRD PARTY LIST
Where your identity data ends up
  • next-api.telecrm.in

    TeleCRM sales-CRM SaaS. Receives the lead record (name, phone, email, country) via a hardcoded bearer token, tagged with source WACA.

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

    AWS API Gateway backing a second CRM on the Joyz.ai platform. Receives the same identity fields via a hardcoded admin-scoped token.

  • us-central1-waplugin-34798.cloudfunctions.net

    The vendor's own Amplitude analytics relay. Every logged event also carries your name, phone number, email and country as user properties.

05EvidencePLAIN NOTE
Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

Data recipients

next-api.telecrm.in6vokqgdnu4.execute-api.ap-south-1.amazonaws.com (Joyz.ai)us-central1-waplugin-34798.cloudfunctions.net (Amplitude analytics)
Updated 20 September 2026kcinfgcogmdemkgolpacnjmkmmmehdbh