All posts

Fishing for Malware: Catching a Trojanized AI Tool Hiding in npm

I was playing chess on chess.com when Dependency Guardian caught its first live malware.

I'd just turned on a feature that watches every package published to npm in real time. Expected it would take a day or two to flag anything real. Two minutes later, I alt-tabbed to a score of 100 out of 100 on a package called ahmed_salem_ph, published less than thirty minutes earlier. My first instinct: false positive. So I stopped the scan and started pulling at the code.

What I found was a trojanized AI coding assistant with a system-wide Windows keyboard hook, two layers of anti-tamper obfuscation, and a detection rate on VirusTotal of 3 out of 76 antivirus engines.

What was inside

Six files. A 219KB Windows executable inside a JavaScript package. A .env file shipping a real OpenAI API key to a public registry. And 38KB of obfuscated JavaScript rigged so that modifying a single character changes the decryption key and the payload silently fails to decrypt.

The entry point launches everything through PowerShell with -WindowStyle Hidden and -ExecutionPolicy Bypass. The only output the user sees is the word "ERROR," printed regardless of what actually happens. Behind that fake error message, a hidden process installs a global keyboard hook, waits for Ctrl+Up, reads the clipboard, sends the contents to GPT-5.4 through the OpenAI API, and pipes back the response.

No visible window. No browser tab. Your clipboard holds passwords, tokens, private messages, code with secrets. All of it sent to an external API without consent.

What it actually was

Here's the twist: this is a cheating tool for a coding class.

The system prompt covers what echo does, how variables work, basic HTML forms, introductory MySQL. High school or early bootcamp material. It specifically instructs the model to return raw code only, no comments, no markdown. Just the answer.

Student sits in class. Copies the exam question. Presses Ctrl+Up. The hidden process sends it to GPT-5.4 and returns raw PHP. No visible window, no ChatGPT in the browser history, nothing for the proctor to see.

That's why the window is hidden. That's why it runs through npx without a permanent install. That's why the author published six versions in 48 minutes, refining the prompt each time. They were testing it before using it.

Someone assembled this from parts: a tutorial-grade C++ keyboard hook, a free JavaScript obfuscator, and about 50 lines of OpenAI glue code. Then shipped it to a public registry with their own API key in plaintext. The skill mismatch is almost funny. The threat isn't. A system-wide keyboard hook that reads your clipboard and sends it to an external API is a trojan regardless of who built it or why.

What caught it, and what didn't

Before I started pulling the package apart, I checked Socket.dev. They'd already indexed it. Their score: 65 out of 100, not flagged as malware. Their supply chain signals were picking up the same red flags, but the overall score mixes supply chain risk with quality, maintenance, vulnerability, and license metrics. A fresh package scores well on maintenance. MIT license scores well on licensing. The malicious signals got averaged into a passing grade.

73 out of 76 antivirus engines on VirusTotal missed the binary. It imports only basic Windows DLLs with no networking libraries. Scan it in isolation and it looks like a harmless console app. The malicious behavior only emerges when you look at the whole package together: binary captures keystrokes, JavaScript handles exfiltration.

Dependency Guardian scored it 100. Four behavioral detectors fired: binary_smuggler because a Windows executable shipped inside an npm package with no legitimate reason. obfuscated_exec because obfuscated code combined with child process execution. obfuscated_binary for the executable itself. ghost_package_risk because no repository, no README, and a description that read "a game help you."

None of that required understanding what the obfuscated code does. It flagged the shape of the attack. Why does a package described as "a game help you" need a hidden .exe and 38KB of obfuscated JavaScript? Advisory databases can't answer that question because nobody's reported this package yet. Antivirus can't answer it because the binary looks clean in isolation. Composite scores can't answer it because they average the signal away.

Behavioral analysis answered it in two minutes.

This wasn't a fluke. Dependency Guardian is tested against thousands of known malicious npm and PyPI packages. Full accuracy data is at westbayberry.com/benchmark.

Try it

The free tier includes 1,000 scans per month with all 35 detectors enabled. Install the CLI with npm i -g @westbayberry/dg, or connect the GitHub App for automatic PR scanning with zero configuration.

Get started free →