You probably haven’t heard of imgdecode. It bills itself on npm as a “native image buffer decoder for Node.js build pipelines” — and as of today, October 17, 2026, you can still npm install it. But imgdecode doesn’t decode images. Per a SafeDep technical analysis published October 13, the imgdecode npm package is a malicious shellcode loader for the MALFEX supply-chain campaign: a nine-line JavaScript file that loads a Windows DLL, downloads encrypted shellcode from GitHub, and injects it into a new dllhost.exe process. I checked the npm registry this evening — version 1.0.3 is still live and installable right now.
This post is the practical companion to the research: what the package actually does, the 60-second version of the MALFEX campaign, exactly how to check your lockfiles for it, what to do if you find it, and a five-minute dependency audit to run before your next npm install.
What the imgdecode npm package actually does
SafeDep’s analysis is unusually detailed, so here is the infection chain as they documented it — every step verified in the original writeup:
- The trigger is
require(), not installation. The package’sindex.jsis nine lines long. It loads a prebuilt native addon atprebuilds/win32-x64/imgdecode.node. From version 1.0.1 onward, the malicious routine runs inside the N-API module initialisation function — meaning it fires the moment your Node.js process callsrequire('imgdecode'). No install script is needed, and the tarball contains nobinding.gyp, so no build step happens either. The prebuilt DLL just loads. - It only fires from an interactive Windows console. The DLL’s first move is to grab the standard output handle and call
GetConsoleMode. That call fails when stdout is piped or redirected — which is exactly how CI runners, automated sandboxes, and most build tools work. SafeDep’s inference: the check deliberately favours execution from a developer’s terminal, where the valuable credentials live. - The payload comes from GitHub, encrypted. Strings in the DLL are encoded with a one-byte XOR key (
0x37). Decoded, they reveal the payload URL —raw.githubusercontent.com/cavecrew/proj/main/banner.jpg— and the passphrasenif-runtime-2027. The DLL downloads the file with WinINet, then decrypts it with AES-256-CBC, deriving the key from the SHA-256 hash of that passphrase. The first 16 bytes of the response are the IV; the rest is ciphertext. - Injection into
dllhost.exe. For any content that doesn’t decrypt to a JPEG or PNG signature, the DLL starts%SystemRoot%\System32\dllhost.exesuspended, allocates memory in it (VirtualAllocEx), writes the decrypted bytes (WriteProcessMemory), flips the memory to execute-read (VirtualProtectEx), and launches a remote thread at the first byte (CreateRemoteThread). The main thread ofdllhost.exestays suspended; only the injected thread runs. If injection fails, a fallback routine runs the shellcode inside the Node.js process itself. - There is even a kill switch. After decryption, the DLL checks whether the plaintext starts with a JPEG or PNG signature — and if so, it stops. Inference: the operator could deactivate the loader by serving encrypted content that decrypts to a real image.
Two honesty notes, because the authenticity bar matters here. First, SafeDep could not recover banner.jpg itself — the cavecrew GitHub account was already returning 404s at analysis time. They did recover a sibling file, banner.png, which decrypts (same key, same IV layout) to a Go downloader whose hash matches Checkmarx’s MALFEX indicator list — and on October 13 the stage-3 server at 104.234.65.75:700 was still serving a 71.5 MB Node.js stealer named movinlike. The native loader’s link to that payload is a strong inference, not a confirmed chain. Second, the payload must start with raw shellcode rather than a PE header — the injector starts the thread at byte zero of the buffer.
And one detail for the comedy-tragedy file: the DLL ships with its debug symbols path still inside. It reads C:\Users\Administrator\Desktop\npm\imgdecode\build\Release\imgdecode.pdb. Opsec was not this operator’s strong suit.
The MALFEX campaign in 60 seconds
MALFEX is the name Checkmarx and CloudSEK gave, in early October 2026, to a campaign in which one operator — assessed to be a lone Portuguese-speaking threat actor — has been publishing malicious npm packages since August 6, 2023. The numbers: 12 packages published across five npm accounts, eight confirmed malicious, 40,767 downloads as of October 1. Three distinct infection pathways: a loader for Overlord RAT (a Go remote-access trojan that pulls its C2 address from Solana transactions), a chain that installs movinlike (a Node.js stealer targeting Discord, browsers, Telegram, and crypto wallets), and a plain downloader.
The operator’s signature move is speed. When npm removed native-runner and img-to-native on September 28, replacements were up within two days: runhelper and imgbundle on September 30, then imgdecode 1.0.0 that same evening. When those got removed too, version 1.0.3 of imgdecode — published October 11, six days after the Checkmarx disclosure — dropped the imgbundle dependency entirely and went standalone.
Here is the current status, verified against the npm registry this evening (October 17, 2026):
imgdecode— STILL LIVE, latest 1.0.3, fully installable.function-flag,cdn-img-fetch,imgbundle— removed; npm replaced each with a0.0.1-securityholding package (October 7–8). Installing those names now gets you a placeholder.runhelper— unpublished entirely (September 30).
imgdecode is the last MALFEX package standing on npm. That is the news value of this post: the window to warn people is right now, while the package is still installable.
Step 1: Check whether imgdecode is in your projects
Run these in every Node.js project you own, on your machine and in your CI cache directories. Start with the direct question:
npm ls imgdecode
If it’s installed anywhere in the tree, that command tells you which package pulled it in. Then check your lockfiles directly — a lockfile pins the package even if node_modules was cleaned:
grep -n '"imgdecode"' package-lock.json
grep -n 'imgdecode' pnpm-lock.yaml yarn.lock
And inspect what’s actually on disk, including the version — anything in the 1.0.x range from publisher malfexteste8 is the malicious package:
ls node_modules/imgdecode 2>/dev/null && node -p "require('./node_modules/imgdecode/package.json').version"
One more place people forget: global installs and CI caches. Check npm ls -g imgdecode, and if your CI caches node_modules between runs, audit the cache too — a poisoned cache re-installs the malware long after the registry takes it down.
Step 2: Check for the rest of the MALFEX package list
Even though npm removed the sibling packages, they can still sit in your node_modules or be pinned in a lockfile from when they were live. Grep for all of them:
grep -nE '"(imgdecode|tlxbnhd|tldriver|mxdriver|img-to-native|native-runner|function-flag|function-color|cdn-img-fetch|imgbundle|runhelper)"' package-lock.json
For pnpm and Yarn lockfiles, drop the quotes and search the bare names. The list above covers all eight confirmed-malicious packages from the Checkmarx/CloudSEK disclosure plus the three replacement packages (imgbundle, runhelper, imgdecode) from the September 30 wave.
A word of caution about what won’t catch this: npm audit. The audit command checks your tree against the GitHub Advisory database — vulnerability records for legitimate packages. imgdecode isn’t a vulnerable version of a real package; it’s malware wearing a package costume. Searching OSV today turns up MAL records for its siblings (imgbundle, runhelper, cdn-img-fetch, img-to-native, native-runner) but none for imgdecode itself. Audit tooling is at its worst against exactly this class of threat, so the manual lockfile check above is the one that matters.
Step 3: If you find one, treat the machine as compromised
SafeDep’s incident-response guidance is blunt: a Windows workstation that loaded imgdecode should be treated as potentially compromised. Here’s the condensed version, in order:
- Isolate the workstation and capture memory before rebooting — the native loader runs its shellcode in memory, so a reboot destroys evidence.
- Check for persistence. The MALFEX stealer maintains persistence via a Registry Run key and a scheduled task named
MicrosoftEdgeUpdateService. Also look fordllhost.exeprocesses that are children ofnode.exe— that parent-child relationship should never happen on a clean machine. - Rotate every credential the machine touched. Per SafeDep’s list: browser passwords, session cookies, Discord tokens, cloud keys, SSH keys, and npm and GitHub tokens. The stealer harvests seed phrases,
.kdbx,.pem,.ppkand.envfiles, browser data, Discord and Telegram data, and crypto-wallet and gaming-app data — and it phones home over a WebSocket C2 at104.234.65.75:7007for file transfer, process control, and shell execution. Assume exfiltration, not just infection. - If the machine held crypto seed phrases, move the funds to new wallets. Do this before anything else on a clean machine.
- Compare local Security and Sysmon logs against external logs — the recovered stealer shows evidence of log-tampering capability, so local logs alone may lie to you.
One more thing worth knowing about the stealer’s capabilities: SafeDep’s analysis of the movinlike sample is partly inference from string tables and embedded scripts — they did not recover the bytecode control flow. Treat the capability list as “the code contains the machinery for this” rather than “confirmed working.” The anti-analysis checks (VM, sandbox usernames, x64dbg, procmon, wireshark), the Discord token theft and discord_desktop_core patching, and the Chromium password/cookie targeting are all in the strings. That is plenty.
Why imgdecode is nastier than your average npm malware
Most malicious npm packages follow a tired script: a postinstall hook downloads a binary. imgdecode breaks the script in five ways, and each one defeats a common defence:
- No install script, no build step. The tarball has no
binding.gypand the archived manifest has no scripts — the prebuilt DLL loads with plainrequire(). As SafeDep puts it, “controls that only block install scripts do not stop this execution path.” If your defence strategy was--ignore-scripts, this package walks right past it. - The console check evades CI and sandboxes. Because
GetConsoleModefails on redirected stdout, the loader stays dormant in CI runners, automated sandboxes, and most dynamic-analysis environments. It wakes up on a developer’s laptop — where the credentials are. - A compiled DLL evades JavaScript-focused scanners. The malicious logic lives in machine code behind a nine-line JS shim. Scanners that analyse package JavaScript see nothing.
- The README lies. The package README documents an
exec()function that version 1.0.3 doesn’t even export — misleading documentation, the same trick the siblingrunhelperpackage used. If you skimmed the docs, you’d think it was a normal utility. - The operator iterates in days, not months. Removal on September 28, three replacement packages on September 30, a standalone rewrite on October 11. Blocking one package name is whack-a-mole; the publisher handle (
malfexteste8) and the campaign patterns are the real signal.
A five-minute audit before your next npm install
You can’t realistically reverse-engineer every dependency, but the MALFEX packages share tells that a quick audit catches. Make this a habit for any package you haven’t installed before:
- Read the publisher.
imgdecode‘s sole maintainer ismalfexteste8, a single-purpose account created to publish malware. Legitimate packages usually have maintainers with history — multiple packages, linked GitHub profiles, years of activity. A one-package account with a fresh creation date is a red flag. - Check the publish timeline.
npm view imgdecode timeshows four versions in twelve days, three of them within 48 hours of first publication. Rapid-fire version bumps on a brand-new package often mean the author is iterating on evasion, not features. - Peek inside the tarball without installing it.
npm pack imgdecode --dry-runlists the package contents without downloading or running anything. A “JavaScript image decoder” shipping aprebuilds/win32-x64/*.nodebinary and no source for it should stop you cold. Also checknpm view imgdecode scriptsand thegypfileflag — native addons with prebuilt binaries and no visible build are the classic malware shape. - Read the code you’d actually load. You don’t need to reverse a DLL: open the
index.jsentry point and check that it matches the README.imgdecode‘s README promised anexec()function the code never exported — a five-second read that exposes the disguise. - Sanity-check the download curve. Under a thousand downloads on a package claiming to be build-pipeline infrastructure, all clustered in the last two weeks, is not organic adoption. Compare against established alternatives before reaching for the new shiny thing.
Make the checks automatic
Manual audits don’t scale, so wire the important ones into your workflow:
- Gate installs on a scanner that does malware detection, not just vulnerability matching — Socket, Snyk, or GitHub’s dependency review with malware alerts. Plain
npm auditis necessary but not sufficient, as theimgdecodecase proves. - Review lockfile diffs in CI. A new dependency appearing in
package-lock.jsonshould be as review-worthy as a code change. Flag additions of packages with single-maintainer publishers or prebuilt binaries. - Pin and verify. Lockfiles plus
npm ci(nevernpm installin CI) stop dependency confusion and range drift, though they don’t stop a malicious pinned package — which is why the review step matters. - Consider an allowlist for sensitive environments. On build machines that handle signing keys or production credentials, an explicit list of approved dependencies beats any denylist. MALFEX’s whole strategy is minting new names; you can’t denylist what doesn’t exist yet.
The uncomfortable truth this campaign demonstrates: npm’s removal process is reactive, and the operator is faster than the takedowns. imgdecode went from disclosure (October 5) to a standalone rewrite (October 11) to still-installable (October 17) while everyone was watching. Your lockfile is the last line of defence — check it.
Facts verified against the npm registry, the SafeDep technical writeup, and security-press coverage on October 17, 2026. The package status (“still live”) reflects the registry state at that time; re-check before acting on it.
Further Reading & References
- MALFEX Moves Its npm Loader into a Native Windows DLL — SafeDep’s original technical analysis (October 13, 2026): the DLL teardown, decryption details, and response guidance this post is built on.
- MALFEX npm Package ‘imgdecode’ Hides Shellcode Loader in Windows Native Addon — The Hacker News coverage (October 16–17, 2026): the campaign timeline and the 979-download figure.
- MALFEX npm Supply-Chain Attack: RAT & Stealer in 8 Packages — Orca Security’s campaign-level research: 40,767 downloads, the three payload families, and the packages that were still live at disclosure.
- MALFEX npm Malware Campaign: Three Payloads and an Adversary That Signs Their Work — Checkmarx Zero’s original disclosure (October 5, 2026).
- MALFEX npm Malware Campaign Racks Up 40k Downloads — SparTech’s writeup with the
function-flagdetail (malicious since July 2025, 37,419 downloads). - MALFEX Campaign: ‘imgdecode’ npm Package Deploys Shellcode Loader via Windows Native Addon — QPulse: stealer capabilities, C2 details, and persistence mechanisms.
- MALFEX npm Package imgdecode Hides a Windows Shellcode Loader — Hendry Adrian’s summary (October 17, 2026) with response advice for affected developers.
- GhostAction: The GitHub Supply-Chain Attack Devs Should Know About — this morning’s HowToShipIt post on the same-day supply-chain beat; the two stories pair well.



