← Back to news

A backdoor in a LinkedIn job offer

roman.pt|885 points|170 comments|by lwhsiao|Jun 15, 2026

🛡️ 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:

  1. I deployed a disposable VPS via Hetzner.
  2. I cloned the repository onto this isolated instance.
  3. 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:

VariableValue
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: npm installprepare scriptnode app/index.jsrequire(’./test’)RCE\text{npm install} \rightarrow \text{prepare script} \rightarrow \text{node app/index.js} \rightarrow \text{require('./test')} \rightarrow \text{RCE}

// 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 Journalist \rightarrow Node.js & npm Expert

The "journalist" suddenly began debating specific Node versions and aggressively pushing me to run the installation command.

Security Warning


💡 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 install on an untrusted repo without auditing package.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, or postinstall scripts.

I am currently developing Smello, a local-first HTTP traffic inspector designed specifically for Python.