# That LinkedIn Job Offer Hid a Backdoor in npm install

> Source: <https://byteiota.com/linkedin-npm-backdoor-job-offer/>
> Published: 2026-06-16 01:09:37+00:00

A developer received a LinkedIn message last week from a recruiter at a crypto startup. Normal enough. The ask: review a GitHub repository before the technical interview — “just check for deprecated Node modules.” The developer opened the repo. Nothing obvious jumped out. Then, before running `npm install`

, they passed the repo to a read-only AI agent for a first look. The AI found it immediately.

Buried in `app/test/index.js`

— disguised as 250 lines of test boilerplate — was a backdoor. The moment `npm install`

ran, it would execute. No test command needed. Just install. The payload assembled a URL from string fragments, phoned home to an attacker’s server, and ran whatever code came back. The developer, Roman Imankulov, [published the full account today](https://roman.pt/posts/linkedin-backdoor/) — it hit 629 points on Hacker News within hours.

## The Mechanism: What `prepare`

Actually Does

Most developers know to be careful about running `npm install`

on random packages. Almost nobody audits the root `package.json`

before installing an unfamiliar repo. That’s the gap attackers exploit.

npm’s `prepare`

lifecycle script runs automatically on every `npm install`

. No flags, no confirmation. The malicious `package.json`

in Imankulov’s case looked like this:

```
{
  "scripts": {
    "prepare": "node app/index.js",
    "app:pre": "node app/test/index.js"
  }
}
```

That’s it. `npm install`

executes `node app/index.js`

, which loads the test file, which assembles the C2 URL, which runs the second-stage payload. The attack is buried three files deep — none of which look alarming in isolation — and triggered by the most routine command in Node.js development.

A second developer [described a nearly identical scenario on DEV Community](https://dev.to/vladimirnovick/a-linkedin-recruiter-sent-me-malware-disguised-as-a-pre-interview-code-review-2k3j), with a more sophisticated variant: the C2 endpoint was hidden inside a public Google Doc, letting the attacker rotate the destination server without touching the repository. It used `new (Function.constructor)("require", code)`

instead of `eval()`

to evade static analysis tools. The payload targeted environment variables — API keys, AWS credentials, tokens — everything a developer has loaded in their shell.

## Why It’s Convincing

The repository in Imankulov’s case had 39 commits, all attributed to a real full-stack developer who confirmed he’d never worked on the project. The recruiter profile belonged to a real arts journalist, impersonated with technically-worded messages. When Imankulov suggested reading the code before installing, the recruiter pushed back — specifically steering him toward `npm install`

.

This isn’t phishing. It’s social engineering with operational precision.

## The Industrial Scale Behind It

Microsoft tracks this campaign as Sapphire Sleet — a [North Korean state-sponsored threat actor](https://www.microsoft.com/en-us/security/blog/2026/04/16/dissecting-sapphire-sleets-macos-intrusion-from-lure-to-compromise/) also known as Contagious Interview, active since December 2022. In April 2026 alone, the operation spread 1,700 malicious packages across npm, PyPI, Go, and Rust. In March 2026, Axios itself was compromised — versions 1.14.1 and 0.30.4 delivered a cross-platform RAT to developers who had nothing to do with any job interview. They just used a popular library.

The LinkedIn recruitment variant is one branch of the same tree. Individual developer targeting, package ecosystem poisoning, maintainer account compromise — all converging on the same goal: get into a developer’s machine, exfiltrate credentials, establish persistence.

## What to Do Right Now

The most effective immediate action: use `npm install --ignore-scripts`

when reviewing unfamiliar repositories. This prevents all lifecycle scripts — `prepare`

, `postinstall`

, `preinstall`

— from running. Make it permanent:

```
npm config set ignore-scripts true
```

Before installing anything from an unknown source, open `package.json`

and check the `scripts`

field. Any entry running a file from the project’s own source tree — especially under `prepare`

or `postinstall`

— is worth tracing before you touch install. For repos from unknown sources, use a throwaway environment: a Docker container, a VM, or a remote VPS. The attack only executes locally. Reading code on GitHub is safe.

The longer fix is arriving. [npm v12, shipping in July 2026](https://github.blog/changelog/2026-06-09-upcoming-breaking-changes-for-npm-v12/), flips `allowScripts`

to `false`

by default. Lifecycle scripts will require explicit approval via `npm approve-scripts`

. We’ve covered [what npm v12 breaks and how to prepare](https://byteiota.com/npm-v12-breaking-changes-what-breaks-in-july-2026/) — the recruiter backdoor is exactly the attack this change is designed to stop.

Until July, the rule is simple: any LinkedIn recruiter asking you to clone and install a repo before a technical interview should be treated as untrusted input. Read the `package.json`

first. Run in a sandbox. Ask why they’re specifically pushing `npm install`

.

The answer is usually in that `prepare`

script.
