Is WaSendPlus - WhatsApp CRM & ChatGPT safe?
WaSendPlus is high risk. After license validation, WaSendPlus posts a machine registration record to Keygen built from the WhatsApp phone number, popup device name, OS family, and browser version. Not reached live; test license rejected. Evidenced from source.…
Who publishes itwasenderplus - no other listings under this identity
wasenderplus - no other listings under this identity
What this publisher told the store about itself, and the other listings that told it the same thing.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Device registration sends phone and system details
After license validation, WaSendPlus posts a machine registration record to Keygen built from the WhatsApp phone number, popup device name, OS family, and browser version.
Not reached live; test license rejected.
Evidenced from source.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You activate WaSendPlus with a license key and device name.
The extension prepares a machine registration record that links your WhatsApp phone number to that device.
- WhatsApp phone number+1 555 010 0199 (illustrative)
This ties the registration record back to the phone number stored by the extension.
- Device nameWork laptop (illustrative)
This is the name you typed for the device during activation.
- Operating systemWindows 10 (illustrative)
This describes the kind of computer or operating system where you activated the extension.
- Browser detailsChrome 126.0.6478.127 (illustrative)
This records the browser family and version used for the activation.
- License credentialAuthorization: License WASEND-2026-ABCD (illustrative)
The registration request authorizes itself with the license key returned by the validation response.
- Accept
- application/json
- Content-Type
- application/json
- Authorization
- License <redacted>
Activation branch that posts the machine record
Formatted popup activation function
popup/popup.jsfunction y() { var n = document.getElementById("activateLicenseKey").value, r = document.getElementById("activateDeviceName").value; if (n.length < 4 && r.length < 4) return alert("Please name this device more than 4 words ."), void l("Activate", e, a, t); chrome.storage.sync.get({ phoneNumber: "" }, (async function(d) { const m = d.phoneNumber; m ? await w(key = n, fingerprint = m).then((async d => { const { meta: u, data: y, errors: g } = d; if (u.valid) console.log(u.valid), await chrome.storage.sync.set({ licenseKey: n }), window.location.replace("./options.html?isWeb=true"); else if (null != y) await async function(e, t, n, a, o, i) { const r = await fetch(`https://${s}/v1/accounts/${c}/machines`, { method: "POST", headers: { Authorization: `License ${e.attributes.key}`, "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify({ data: { type: "machine", attributes: { fingerprint: t, name: n, platform: a, metadata: { browser: o, version: i } }, relationships: { license: { data: { type: "license", id: e.id } } } } }) }), { data: l, errors: d } = await r.json(); return { data: l, errors: d } }(y, m, r, i.os.family, i.browser.family, i.browser.version).then((async o => { const { data: i, errors: c } = o; if (c) return console.log(c), console.log(c[0].code, c[0].title, c[0].detail), void alert(c[0].detail); l("Activation Successful !", e, t, a), await chrome.storage.sync.set({ licenseKey: n }), await chrome.tabs.sendMessage(v.id, { message: "activationKeySuccess" }).catch((e => { console.error(e) })), window.location.replace("./options.html?isWeb=true") })); else if (!u.valid) return console.log(u.code, u.detail), alert(u.detail), void l("Activate", e, o, t) })) : (u("PLease login to Whatsapp Web before you activate license !"), l("Activate", e, o, t)) })) }- api.keygen.sh
Keygen license-management API receives the machine registration record for the WaSendPlus account telegramrocket-80ee1bb5.
AI reply sends WhatsApp chats to OpenAI
WaSendPlus's AI reply flow in WhatsApp Web reads recent message elements, builds a prompt with the text and sender labels, and sends it to the background script, which posts it to ChatGPT or OpenAI's API.
Not triggered live; not logged in.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You ask the extension to draft an AI reply inside WhatsApp Web.
The extension reads recent messages from the current chat and turns them into a prompt.
- Recent message text1: Are we still meeting at 5? (illustrative)
This can include private conversation content from the WhatsApp chat you are viewing.
- Sender labelsMe: Running five minutes late (illustrative)
The prompt keeps track of which recent messages were yours and which came from contacts.
- Message count settingaiReplyLastMessage = 10 (illustrative)
The extension limits how much chat history it reads using a stored setting, defaulting to recent messages when unset.
- Reply instructionAs me, give an utterance completing the following chat conversation flow. (illustrative)
The generated prompt tells the remote service to continue the conversation in your writing style.
- Content-Type
- application/json
- Authorization
- Bearer <redacted>
- Content-Type
- application/json
- Authorization
- Bearer <redacted>
Chat scraping and forwarding path
Formatted chat-history extractor
content/contentScript.jsasync function n(n) { const o = []; let i = n.querySelectorAll("._amk4._amkd._amk5"); const { aiReplyLastMessage: s } = await t(["aiReplyLastMessage"]) || { aiReplyLastMessage: 10 }, l = new e; i = Array.from(i).slice(-s), i.forEach((e => { let t = ""; const n = e.querySelectorAll(".copyable-text"); if (n.length > 0) n.forEach((e => { const n = e.getAttribute("data-pre-plain-text"); if (null !== n) if (null !== e.closest(".message-out")) t += "Me: "; else { let e = n.replace(/\[.*?\]\s*/, "").slice(0, -2); l.add(e); const a = l.getPosition(e) + 1; t += a + ": " } else { const n = a(e); void 0 !== n && (t += n) } })); else if (e.classList.contains("ATT")) { const n = e.parentNode.previousElementSibling.getAttribute("aria-label"); if (null !== n) if (null !== e.closest(".message-out")) { t += "Me: "; const n = a(e); void 0 !== n && (t += n) } else { let o = n.replace(/\[.*?\]\s*/, "").slice(0, -1); l.add(o); const i = l.getPosition(o) + 1; t += i + ": "; const s = a(e); void 0 !== s && (t += s) } } 0 !== t.length && o.push(t) })), console.log(o); const r = o[o.length - 1]; let c = !1; return r?.includes("Me:") && (c = !0), { chatHistoryShort: o.join("\n"), lastIsMine: c } }Formatted prompt sender
content/contentScript.jsu = async function() { const { chatHistoryShort: t, lastIsMine: a } = await n(e); N({ type: "info", level: 135, function_name: "parseHtmlunction", messages: `last is mine :${a}` }); let o = function(e, t) { let n; "default" == l ? n = e ? "As me, give a double texting utterance completing the following chat conversation flow. Use Emoji and my writing style and do not repeat the contents of the last utterance:\n\n" : "As me, give an utterance completing the following chat conversation flow. Use Emoji and my writing style:\n\n" : "custom" == l && (n = r); let a = n + t + "\n\nMe: "; return N({ type: "info", level: 63, function_name: "createPrompt", messages: `${a}` }), a }(a, t); A.setBusy(!0), await chrome.runtime.sendMessage({ message: "sendChatToGpt", prompt: o }) }Formatted OpenAI forwarding handler
background/background.jschrome.runtime.onMessage.addListener((function(s, r) { "sendChatToGpt" === s.message && chrome.storage.local.get({ apiChoice: "webapp" }, (n => { "webapp" === n.apiChoice ? async function(s, r) { try { await (e?.cancel()), e?.releaseLock(), e = null; const n = await async function() { const e = await fetch("https://chat.openai.com/api/auth/session"); if (200 === e.status) { const { accessToken: t } = await e.json(); return t } return null }(); n ? await async function(s, r, n) { const c = { action: "next", messages: [{ id: t(), role: "user", content: { content_type: "text", parts: [r] } }], model: "text-davinci-002-render-sha", parent_message_id: t(), is_visible: !1 }, l = await fetch("https://chat.openai.com/backend-api/conversation", { method: "POST", headers: a(s), body: JSON.stringify(c) }); if (l.ok) try { for (e = l.body.getReader();;) { const { done: t, value: a } = await e.read(); if (t) break; const s = o((new TextDecoder).decode(a)); s && n({ text: s }) } } catch (e) { console.error(e), n({ error: { type: "unknown", message: e.message } }) } else 429 === l.status ? n({ error: { type: "processingError", message: "You have been rate-limited by ChatGPT. Please try again." } }) : n({ error: { type: "processingError", message: "Something went wrong fetching the answer from GPT; Statuscode: " + l.status + "," + l.statusText } }) }(n, s, r): r({ error: { type: "noSessionToken", message: '<a href="https://chat.openai.com/" target="_blank">Click here to log in to ChatGPT</a>!' } }) } catch (e) { console.error(e), r({ error: { type: "unknown", message: e.message } }) } }(s.prompt, (async function(e) { try { "popup" == s.from ? chrome.runtime.sendMessage({ message: "gptResponse", response: e }) : await chrome.tabs.sendMessage(r.tab.id, { message: "gptResponse", response: e }) } catch (e) { console.error("error sending message back to whatsapp", e) } })): async function(t, o) { try { await (e?.cancel()), e?.releaseLock(), e = null; const { apiKey: s, modelSelect: r } = await async function(e) { const t = await new Promise((t => { chrome.storage.local.get(e, (e => { t(e) })) })), a = {}; return e.forEach((e => { a[e] = t[e] })), a }(["apiKey", "modelSelect"]); s && s.length > 0 ? await async function(t, o, s, r) { const n = { model: o, messages: [{ role: "user", content: s }], temperature: .5 }, c = await fetch("https://api.openai.com/v1/chat/completions", { method: "POST", headers: a(t), body: JSON.stringify(n) }); if (c.ok) try { for (e = c.body.getReader();;) { const { done: t, value: a } = await e.read(); if (t) break; const o = (new TextDecoder).decode(a), s = JSON.parse(o); let n = null; if (void 0 !== s.error) { let e = s.error.message; console.log("chatGetAnswerError:", e), n = e } else try { n = s.choices[0].message.content.trim() } catch (e) { n = s } n && r({ text: n }) } } catch (e) { console.error(e), r({ error: { type: "unknown", message: e.message } }) } else 429 === c.status ? r({ error: { type: "processingError", message: "You have been rate-limited by ChatGPT. Please try again." } }) : r({ error: { type: "processingError", message: "Something went wrong fetching the answer from GPT; Statuscode: " + c.status + "," + c.statusText } }) }(s, r, t, o): o({ error: { type: "noApiKey", message: "No api key found" } }) } catch (e) { console.error(e), o({ error: { type: "unknown", message: e.message } }) } }(s.prompt, (async function(e) { try { "popup" == s.from ? chrome.runtime.sendMessage({ message: "gptResponse", response: e }) : await chrome.tabs.sendMessage(r.tab.id, { message: "gptResponse", response: e }) } catch (e) { console.error("error sending message back to whatsapp", e) } })) })) })); let e = null; function t() { return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (e => (e ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> e / 4).toString(16))) } function a(e) { return new Headers({ "Content-Type": "application/json", Authorization: "Bearer " + e }) } function o(e) { const t = /"role":\s*"assistant".+?"parts":\s*\[(.*?)\]/g; let a, o = ""; if (a = t.exec(e), !a) return null; for (; null !== a;) o = a[1], a = t.exec(e); return JSON.parse(o) }- chat.openai.com
ChatGPT webapp endpoint receives the prompt when the extension is configured for webapp mode.
- api.openai.com
OpenAI chat-completions API receives the prompt when the extension is configured for API-key mode.
License activation sends WhatsApp phone number to Keygen license API.
When you activate a license in WaSendPlus, the extension sends your WhatsApp phone number to Keygen's license validation API as a device fingerprint.
The confirmed request goes to api.keygen.sh using the form-entered phone number.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
The user activates a license in the extension.
The extension sends the user's WhatsApp phone number as the license device fingerprint.
- Your WhatsApp phone number+1 415 555 0100 (illustrative)
This value identifies the WhatsApp account or phone number and is used as the license device fingerprint.
What it can do
Permissions this extension asks for, as declared in version 0.3.14. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Read and change your data on openai.com
https://*.openai.com/
Store data in your browser
storage
Act on the current tab, but only after you click the extension
activeTab