A backdoor in a LinkedIn job offer
🛡️ Anatomy of a Malicious LinkedIn Job Offer
By Roman Imankulov (Full-stack Python Developer)
Recently, I was targeted by a sophisticated social engineering attack that began with a simple LinkedIn message. A recruiter from a niche cryptocurrency startup reached out, claiming they needed a lead engineer to fix a "broken proof-of-concept."
After a few days of chatting, she directed me to a public GitHub repository for a technical review, specifically urging me to investigate an issue regarding outdated deprecated Node modules.
🚩 The Red Flag & The Sandbox
While reviewing code is a standard part of many hiring processes, my intuition signaled that something was wrong. To avoid risking my local machine, I adopted a "zero trust" approach:
- I deployed a disposable VPS via Hetzner.
- I cloned the repository onto this isolated instance.
- I utilized an AI agent (Pi) in a strictly read-only capacity.
The agent was configured with only basic file-system tools:
pi --tools read,grep,find,ls
"I asked the agent to scan the codebase and highlight any anomalies."
🔍 Technical Breakdown: The Backdoor
The project appeared to be a standard React frontend paired with a Node.js backend. However, the trap was meticulously hidden in app/test/index.js—a file containing roughly 250 lines of code masquerading as a test suite.
The Obfuscated URL
The attacker didn't hardcode the malicious URL. Instead, they assembled it from fragments to evade simple string searches:
| Variable | Value |
|---|---|
protocol | "https" |
separator | "://" |
subdomain | "rest-icon-handler" |
domain | "store" |
path | "/icons/" |
token | "77" |
bearrtoken | "logo" |
Resulting URL: https://rest-icon-handler.store/icons/77
The Execution Chain
The payload was buried on line 225, hidden amidst walls of commented-out code. This script was designed to execute any arbitrary code sent back from the remote server.
The trigger mechanism followed this logic:
// package.json logic
"scripts": {
"prepare": "npm run app:pre",
"app:pre": "node app/index.js"
}
The npm install command automatically triggers the prepare script, meaning the backdoor executes the moment the developer attempts to set up the environment. The "deprecated modules" request was simply the bait to ensure I ran npm install.
Attack Flow Visualization
🎭 The Stolen Identities
The attackers didn't just steal code; they stole personas to build trust.
Identity #1: The Developer
The commit history (39 commits) was attributed to a legitimate full-stack engineer. I contacted this person under the guise of having inherited the code. He confirmed he had no involvement with the project and had been a victim of GitHub impersonation in the past.
Identity #2: The Recruiter
The recruiter's profile belonged to a prominent arts journalist. There was absolutely nothing technical in her professional history. However, the moment I mentioned I was having trouble installing the project, the persona shifted:
Arts JournalistNode.js & npm Expert
The "journalist" suddenly began debating specific Node versions and aggressively pushing me to run the installation command.
💡 Final Thoughts & Lessons
Even as someone aware of these tactics, it's easy to fall for them during a rushed or exhausting day. This experience reinforced a few key points:
- Paranoia is a Feature: Security hygiene is non-negotiable when dealing with unknown repositories.
- AI as an Auditor: Using a read-only AI agent was significantly faster and more effective than manual review; it spotted the "beginner-style" obfuscation in seconds.
🛠️ Security Checklist for Devs
- Never run
npm installon an untrusted repo without auditingpackage.json. - Use isolated environments (Docker, VPS, VMs) for third-party code reviews.
- Verify the identity of the recruiter via secondary channels.
- Check for suspicious
prepare,preinstall, orpostinstallscripts.
I am currently developing Smello, a local-first HTTP traffic inspector designed specifically for Python.