Is WAScheduler - WA Message Scheduler safe?

Medium risk

WADeck reads your WhatsApp phone number and sends URLs from private conversations to third-party link-preview servers.

The extension reads the signed-in user's phone number from WhatsApp Web's localStorage and transmits it, along with a persistent install ID and browser fingerprint, to an Alibaba Cloud logging endpoint. When a message containing a URL is sent, the extension forwards that URL to one of three external link-preview proxy servers — unaffiliated with WhatsApp — to fetch page metadata.

wadeck-publisherv2.0.4Chrome 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-359
SourceAI SANDBOX

WhatsApp message URLs sent to preview proxy servers

When a WhatsApp Web message contains a URL, WAScheduler sends it to an external proxy: a GET to wajsapi.titanchat.com.br/v1/link-preview/fetch-data.png, with the outgoing message URL in the url query parameter.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You send a WhatsApp Web message that contains a URL.

The extension did this

The extension requests link-preview metadata from an external proxy endpoint.

The URL from the message is placed in the request's query string.

02EvidenceNETWORK CAPTURE
Captured request
GEThttps://wajsapi.titanchat.com.br/v1/link-preview/fetch-data.png?url=https://example.com/article
JSON preview metadata containing title, description, image, and URL fields.
03EvidenceFIELD TABLE
Field sent to the link-preview proxy
FieldValueWhy it matters
Message URL
https://example.com/article (illustrative)This is the web address typed into your WhatsApp message. It can reveal the site or content being discussed in that conversation.
04EvidenceTHIRD PARTY LIST
Configured link-preview proxy hosts
  • wajsapi.titanchat.com.br

    Receives the message URL in a link-preview fetch request; this was the host named in the confirmed verification.

  • cobrancas.uppermesh.com.br

    Configured as an alternate link-preview proxy host in the shipped extension code.

  • wppc-linkpreview.cloudtrix.com.br

    Configured as an alternate link-preview proxy host in the shipped extension code.

05EvidenceCODE COMPARE
The code that does this

The injected WhatsApp helper sends the message URL to a preview proxy

What it actually does
The content script injects the page helpercontent.bundle.js
case 16:
  i["default"].prototype.$injectEl = this.appLoc,
  this.appendScriptTag(e.runtime.getURL("qpqp.js")),
  this.appendScriptTag(e.runtime.getURL("inject.bundle.js")),
  this.appendTagTo(this.appLoc, O.toobarId),
  new i["default"]({
    i18n: b["a"],
    store: this.store,
    render: function(e) {
      return e(s["a"])
    }
  }).$mount("#" + O.toobarId)
Outgoing message text is scanned for the first web linkinject.bundle.js
t.prepareLinkPreview = function() {
  var e = _asyncToGenerator(_regeneratorRuntime.mark((function e(t, r) {
    var n, o, i, a;
    return _regeneratorRuntime.wrap((function(e) {
      while (1) switch (e.prev = e.next) {
        case 0:
          if (r.linkPreview) {
            e.next = 2;
            break
          }
          return e.abrupt("return", t);
        case 2:
          if (!r.linkPreview) {
            e.next = 16;
            break
          }
          if (n = "object" == _typeof(r.linkPreview) ? r.linkPreview : {}, o = "chat" === t.type ? t.body : "", !o) {
            e.next = 16;
            break
          }
          if (e.prev = 5, i = (0, f.findFirstWebLink)(o), !i) {
            e.next = 12;
            break
          }
          return e.next = 10, (0, f.fetchLinkPreview)(i);
        case 10:
          a = e.sent, (null == a ? void 0 : a.data) && (r.linkPreview = Object.assign(Object.assign({}, a.data), n));
        case 12:
          e.next = 16;
          break;
        case 14:
          e.prev = 14, e.t0 = e["catch"](5);
        case 16:
          return "object" == _typeof(r.linkPreview) && (t.subtype = "url", t = Object.assign(Object.assign({}, t), r.linkPreview)), e.abrupt("return", t);
        case 18:
        case "end":
          return e.stop()
      }
    }), e, null, [
      [5, 14]
    ])
  })));
  return function(t, r) {
    return e.apply(this, arguments)
  }
}();
The proxy request is built with the message URL in the query stringinject.bundle.js
t.fetchRemoteLinkPreviewData = function() {
  var e = _asyncToGenerator(_regeneratorRuntime.mark((function e(t) {
    var r, n, o, i, a, u, s;
    return _regeneratorRuntime.wrap((function(e) {
      while (1) switch (e.prev = e.next) {
        case 0:
          if (!p[t]) {
            e.next = 2;
            break
          }
          return e.abrupt("return", (l("Link preview found in the cache", t), p[t]));
        case 2:
          r = new TextDecoder, n = f.length - 1;
        case 4:
          if (!(n >= 0)) {
            e.next = 21;
            break
          }
          return o = f[n], l("Fetching link preview using ".concat(o), t), i = "".concat(o, "/v1/link-preview/fetch-data.png?url=") + encodeURI(t), e.next = 10, (0, c.fetchDataFromPNG)(i).then((function(e) {
            return r.decode(e)
          })).then((function(e) {
            return JSON.parse(e)
          })).catch((function() {
            return null
          }));
        case 10:
          if (a = e.sent, null !== a && ("title" in a || "status" in a)) {
            e.next = 14;
            break
          }
          return l("The server ".concat(o, " is unavailable for link preview")), f.splice(n, 1), e.abrupt("continue", 18);
        case 14:
          if (a.title || 200 === a.status) {
            e.next = 16;
            break
          }
          return e.abrupt("continue", 18);
        case 16:
          return u = /^video/.test(a.mediaType), s = {
            title: a.title,
            description: a.description,
            canonicalUrl: a.url,
            matchedText: t,
            richPreviewType: u ? 1 : 0,
            doNotPlayInline: !u,
            imageUrl: a.image
          }, e.abrupt("return", (p[t] = s, setTimeout((function() {
            delete p[t]
          }), 3e5), s));
        case 18:
          n--, e.next = 4;
          break;
        case 21:
          return e.abrupt("return", null);
        case 22:
        case "end":
          return e.stop()
      }
    }), e)
  })));
  return function(t) {
    return e.apply(this, arguments)
  }
}()
The configured server list is randomized before useinject.bundle.js
var o = n(r(17833)),
  i = r(91491),
  a = r(14647),
  u = r(52757),
  s = r(97016),
  c = r(24298),
  l = (0, o.default)("WA-JS:link-preview"),
  f = i.config.linkPreviewApiServers || ["https://cobrancas.uppermesh.com.br:8000", "https://wajsapi.titanchat.com.br", "https://wppc-linkpreview.cloudtrix.com.br"],
  d = 140,
  p = {},
  v = {};
! function(e) {
  for (var t = e.length - 1; t > 0; t--) {
    var r = Math.floor(Math.random() * (t + 1)),
      n = [e[r], e[t]];
    e[t] = n[0], e[r] = n[1]
  }
}(f)

Data recipients

extension-us.us-west-1.log.aliyuncs.comcobrancas.uppermesh.com.brwajsapi.titanchat.com.brwppc-linkpreview.cloudtrix.com.br
Updated 17 September 2026lbjgmhifiabkcifnmbakaejdcbikhiaj