Is Rocket Sender - Bulk WA Messages & Automation safe?

Medium risk

Rocket Sender automates WhatsApp messaging and decides where to activate from a class-name list fetched from its remote config server.

The background script downloads a configuration file from cdn2.waplus.io and stores it locally; the content script (declared on all sites) injects the privileged WhatsApp automation bundle wherever a page matches the remotely supplied CSS class list, so the vendor can change the targeted sites by editing remote config. Background telemetry posts the user's logged-in WhatsApp phone number, a derived country code, install UUID, install time and browser details to an Alibaba Cloud logstore on tracked events. When a user shares a link in WhatsApp, the inject script also forwards the full shared URL to one of three third-party link-preview servers to generate the preview.

rocket-senderv1.5.24Chrome 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

WhatsApp link previews send shared URLs to preview servers

When you send a WhatsApp message with a link, Rocket Sender replaces the preview generator, sending the full URL to one of three servers: cobrancas.uppermesh.com.br, wajsapi.titanchat.com.br, wppc-linkpreview.cloudtrix.com.br.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You compose or send a WhatsApp message that includes a link.

The preview path is reached only when WhatsApp Web can generate a link preview inside an active chat.

The extension did this

The extension builds a preview request that includes the full link.

The link is appended as the `url` query parameter before the request is sent to a configured preview server.

02EvidenceFIELD TABLE
Fields placed into the preview request
FieldValueWhy it matters
Shared link
https://example.com/secret-path (illustrative test URL from the verification recipe)This can reveal the exact page, document, invite, or other destination you are sending in a WhatsApp chat.
Request parameter
url=https://example.com/secret-path (illustrative test URL from the verification recipe)The link is sent in the address bar of the preview request rather than kept only inside WhatsApp Web.
Preview destination
wajsapi.titanchat.com.brA server selected by the extension receives the shared link so it can return preview metadata.
03EvidenceNETWORK CAPTURE
Captured request
GEThttps://wajsapi.titanchat.com.br/v1/link-preview/fetch-data.png?url=https://example.com/secret-path
No live response was captured during traffic testing because WhatsApp Web did not reach a logged-in chat session.
04EvidenceCODE COMPARE
The code that does this

The shipped code wraps WhatsApp's preview generator and builds the preview-server URL

What it actually does
Wrapper that sends the shared URL into the remote preview pathdeobfuscated/inject.js
u.onFullReady((() => {
  (0, l.wrapModuleFunction)(d.getABPropConfigValue, ((e, ...t) => {
    const [r] = t;
    return "link_preview_wait_time" === r ? 7 : e(...t)
  })), (0, l.wrapModuleFunction)(d.genMinimalLinkPreview, (async (e, ...t) => {
    const [r] = t, n = "string" == typeof r ? r : r.url;
    return new Promise((async r => {
      try {
        const e = await (0, c.fetchRemoteLinkPreviewData)(n);
        if (!e) throw new Error(`preview not found for ${n}`);
        const {
          imageUrl: t
        } = e, o = s(e, ["imageUrl"]);
        let i = {};
        t && (i = await (0, c.generateThumbnailLinkPreviewData)(t).catch((() => null))), r({
          url: n,
          data: Object.assign(Object.assign({}, o), i)
        })
      } catch (n) {
        r(await e(...t))
      }
    }))
  }))
}))
Request builder and configured preview serversdeobfuscated/inject.js
t.fetchRemoteLinkPreviewData = async function(e) {
  if (p[e]) return l("Link preview found in the cache", e), p[e];
  const t = new TextDecoder;
  for (let r = d.length - 1; r >= 0; r--) {
    const n = d[r];
    l(`Fetching link preview using ${n}`, e);
    const o = `${n}/v1/link-preview/fetch-data.png?url=` + encodeURI(e),
      i = await (0, c.fetchDataFromPNG)(o).then((e => t.decode(e))).then((e => JSON.parse(e))).catch((() => null));
    if (null === i || !("title" in i) && !("status" in i)) {
      l(`The server ${n} is unavailable for link preview`), d.splice(r, 1);
      continue
    }
    if (!i.title && 200 !== i.status) continue;
    const a = /^video/.test(i.mediaType),
      s = {
        title: i.title,
        description: i.description,
        canonicalUrl: i.url,
        matchedText: e,
        richPreviewType: a ? 1 : 0,
        doNotPlayInline: !a,
        imageUrl: i.image
      };
    return p[e] = s, setTimeout((() => {
      delete p[e]
    }), 3e5), s
  }
  return null
};
const d = i.config.linkPreviewApiServers || ["https://cobrancas.uppermesh.com.br:8000", "https://wajsapi.titanchat.com.br", "https://wppc-linkpreview.cloudtrix.com.br"];
! function(e) {
  for (let t = e.length - 1; t > 0; t--) {
    const r = Math.floor(Math.random() * (t + 1));
    [e[t], e[r]] = [e[r], e[t]]
  }
}(d)
PNG response helper that performs the image GETdeobfuscated/inject.js
t.fetchDataFromPNG = function(e) {
  return new Promise(((t, r) => {
    const n = new Image;
    n.crossOrigin = "anonymous", n.src = e, n.onerror = r, n.onload = function() {
      const e = document.createElement("canvas"),
        r = e.getContext("2d");
      e.width = n.naturalWidth, e.height = n.naturalHeight, r.drawImage(n, 0, 0);
      const o = r.getImageData(0, 0, e.width, e.height).data,
        i = new Uint8Array(Math.floor(3 * o.length / 4));
      for (let t = 0, n = 0; t < o.length; t += 4) i[n++] = o[t], i[n++] = o[t + 1], i[n++] = o[t + 2];
      let a = BigInt(0);
      for (let t = 0; t < 8; t++) a = (a << BigInt(8)) + BigInt(i[t]);
      const s = Number(a);
      t(i.subarray(8, 8 + s))
    }
  }))
}
05EvidenceTHIRD PARTY LIST
Preview servers that can receive the shared URL
  • cobrancas.uppermesh.com.br

    Fallback preview API server from the shipped server list; the code appends `/v1/link-preview/fetch-data.png?url=` and the shared link.

  • wajsapi.titanchat.com.br

    Fallback preview API server from the shipped server list; the code appends `/v1/link-preview/fetch-data.png?url=` and the shared link.

  • wppc-linkpreview.cloudtrix.com.br

    Fallback preview API server from the shipped server list; the code appends `/v1/link-preview/fetch-data.png?url=` and the shared link.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI SANDBOX

Remote config picks which sites get the WhatsApp automation bundle

Rocket Sender's content script runs on every site, checking CSS class names from cdn2.waplus.io (refreshed every 5 min); a matching class injects bundle inject.js.

This activates only on web.whatsapp.com today; the gate is remote config.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open a website in your browser.

The content script is registered on <all_urls>, so it runs on the page regardless of which site it is.

The extension did this

The extension checks the page against a class-name list it downloaded from the developer's server, and injects its automation bundle if the page matches.

The list is fetched from cdn2.waplus.io and refreshed every five minutes, so the developer controls which pages activate the automation.

02EvidenceCODE COMPARE
The code that does this

The content script gates injection on a remotely supplied class list

What it actually does
content.js — readablecontent.js
async function maybeInject() {
  if (alreadyInjected) return;
  const { zbaseConfig } = await chrome.storage.local.get(["zbaseConfig"]);
  const { class_list } = zbaseConfig?.data?.config
    ?.find(e => e.name === "WAPI Class")?.params || [];
  for (const cssClass of class_list) {
    if (document.querySelector(`.${cssClass}`)) {   // page DOM matches a remote class
      alreadyInjected = true;
      await injectScript("/inject.js", "inject");   // add privileged bundle to the page
      window.localStorage.setItem("rocketSender", true);
      await startAutomation();
      break;
    }
  }
  if (!alreadyInjected) setTimeout(maybeInject, 2000); // re-poll the DOM
}

function injectScript(path, id) {
  return new Promise((resolve, reject) => {
    const s = document.createElement("script");
    s.id = id;
    s.src = chrome.runtime.getURL(path);            // inject.js from the extension bundle
    s.onload = resolve; s.onerror = reject;
    document.head.appendChild(s);
  });
}
03EvidenceCODE COMPARE
The code that does this

The background service worker fetches and caches the remote class list

What it actually does
function refreshConfig() {
  chrome.storage.local.get(["zbaseConfig", "configLastFetchTime"], (cached) => {
    const now = Date.now();
    const TTL = 300000; // 5 minutes
    const stale = !cached.zbaseConfig || !cached.configLastFetchTime
               || now - cached.configLastFetchTime > TTL;
    if (stale) {
      fetch("https://cdn2.waplus.io/extensiton/rocket-sender/config/config.json",
            { cache: "no-store" })
        .then(r => r.json())
        .then(cfg => chrome.storage.local.set({
          zbaseConfig: cfg,
          configLastFetchTime: now,
        }));
    }
  });
}
04EvidenceNETWORK CAPTURE
Captured request
GEThttps://cdn2.waplus.io/extensiton/rocket-sender/config/config.json
Observed during dynamic analysis (HTTP 200). The response was stored as chrome.storage.local key zbaseConfig. Its 'WAPI Class' entry carried params.class_list = [x78zum5, xdt5ytf, x5yr21d, _ak9y, _ak8q], the CSS classes that gate where inject.js activates.
Headers
Cache-Controlno-store
05EvidenceTEMPORAL PATTERN
When this fires
Every 5 minutes

The background service worker re-fetches the remote class list whenever the cached copy is older than five minutes, so the set of pages that activate the automation can change roughly every five minutes without an extension update.

06EvidencePLAIN NOTE
What dynamic analysis observed

During dynamic analysis the background request to cdn2.waplus.io returned the class list, and inject.js was injected only on web.whatsapp.com, whose DOM contains those classes. After navigating to example.com and Wikipedia (which contain none of the listed classes), no further injection occurred. This confirms the activation is controlled by the remote class list rather than the <all_urls> manifest pattern. The injected code (inject.js) ships inside the extension and depends on WhatsApp Web internals, so the remote config controls targeting, not the code body.

Data recipients

cdn2.waplus.ious-west-1.log.aliyuncs.comcobrancas.uppermesh.com.brwajsapi.titanchat.com.brwppc-linkpreview.cloudtrix.com.br
Updated 17 September 2026hmpoalkkjnapmpfgleamekjhhfenpodc