DevTap npm Typosquatting Attack

DevTap npm Typosquatting Attack: Six Malicious Packages Target Developer Workstations

TL;DR

Between April 1 and May 3, 2026, a single npm publisher, user0001, registered with the unverified Gmail address tanvisoul9@gmail.com, quietly pushed six packages with deliberately bland, infrastructure-sounding names: centralogger, dom-utils-lite, node-fetch-lite, connector-agent, node-gyp-runtime, and node-env-resolve.

The latest two versions of node-env-resolve, 1.0.7 and 1.0.8, were flagged by Xygeni’s Malware Early Warning (MEW) system on May 2 and confirmed malicious on May 3.

The implant is unusual for what it is not: there are no Telegram bots, no OAST callbacks, no obfuscation, and no attempt to grab AWS credentials at install time. Instead, postinstall.js plants a Windows boot-persistence entry, using an HKCU Run key that launches wscript.exe against a VBS stub. Then it spawns a detached Node.js agent that bundles modules for microphone capture, browser-history theft, screenshotting, and mouse/keyboard simulation.

We are calling this cluster DevTap, after the kit it leaves running on a developer’s machine.

All six packages were live on npm at the time of writing. We have submitted them to the registry’s abuse channel. Defenders should pull any installs from the publisher pending removal.

The Cluster: Six Packages, One Publisher

The publisher account user0001 is unverified. It has no SCM verification, no domain-bound email, and no prior history. The Gmail handle tanvisoul9 does not appear in any other npm publisher record we could find.

All six packages list the same maintainer, were published from the same account, and share the same package.json boilerplate. There is no repository, no homepage, and no description longer than a single line.

The naming strategy is the interesting part. Unlike a classic dependency-confusion or typosquat campaign, none of these names target a specific upstream library. Instead, they are calibrated to disappear into a real package.json or npm ls output.

Package First Published Latest Version Versions Role in the Cluster
centralogger 2026-04-01 12:46 UTC 1.0.9 5, from 1.0.5 to 1.0.9 Cluster’s “logging utility” cover; earliest publish
dom-utils-lite 2026-04-14 07:36 UTC 1.0.3 3 Generic DOM-helper cover
node-fetch-lite 2026-04-19 10:22 UTC 1.0.2 3 Mimics node-fetch family
connector-agent 2026-04-25 05:12 UTC 1.0.0 1 Generic-name “agent”
node-gyp-runtime 2026-04-25 05:17 UTC 1.0.0 1 Mimics native-module build tooling
node-env-resolve 2026-04-25 05:21 UTC 1.0.9 10, from 1.0.0 to 1.0.9 Active dropper; full implant

Names like centralogger, node-fetch-lite, and node-gyp-runtime are designed to look uncontroversial in code review. They sound like things that would already be in a project’s dependency tree.

Combined with a fresh, unverified publisher and missing repository links, they form a recognisable pattern: an actor seeding low-friction names into the registry, then iterating quickly on the one that matters. In this case, node-env-resolve received ten versions in eight days.

What Lands on a Windows Dev Box

The malicious chain on node-env-resolve:1.0.8 is short, direct, and unusually feature-rich for an npm RAT.

Postinstall: Persistence and Detached Agent

package.json declares a single install hook:

{ "scripts": { "postinstall": "node postinstall.js" } }

postinstall.js does three things in order.

First, it stages an installation directory outside the npm tree. The path is computed at runtime in the script as INSTALL_DIR. Then it runs an internal execSync('npm install --production --silent ...') inside it to pull the implant’s runtime dependencies. This puts the agent on disk somewhere a manual node_modules audit will not find.

Second, it writes a VBS launcher and registers it for boot persistence:

reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run \
    /v ${AGENT_NAME} \
    /t REG_SZ \
    /d "wscript.exe \"${vbsPath}\"" /f

wscript.exe is the Windows Script Host, a signed Microsoft binary that runs .vbs files without a console window. That is precisely why it is favoured for autorun stubs.

There is also a matching reg delete path inside src/index.js, suggesting the implant is designed to be cleanly removable on operator command.

Third, it spawns a detached child process:

spawn(node, [path.join(INSTALL_DIR, 'src/index.js')], {
  detached: true,
  env: { ...process.env, SERVER_URL }
})

Two details matter here.

SERVER_URL is read from the environment. As a result, the C2 endpoint is configurable per deployment and is not baked into the package. That small but deliberate choice defeats most static-IOC sweeps.

Also, the spawned child inherits the full parent environment. Therefore, any NPM_TOKEN, AWS_*, GITHUB_TOKEN, or SSH-agent socket present in the developer shell at install time travels into the long-lived agent’s memory.

What the Agent Ships With

The bundled src/ tree includes three modules whose names alone tell the story: audioCapture.js, browserHistory.js, and systemInfo.js.

The runtime dependency list, resolved during the staged npm install, corroborates them.

This approach turns a chaotic incident into a controlled response.

Instead of reacting blindly, teams operate with clear prioritization and fast remediation.

Dependency What It Is For in This Context
screenshot-desktop Periodic screen capture
@nut-tree-fork/nut-js Mouse and keyboard automation / input simulation
better-sqlite3 Direct reads of Chrome, Edge, and Firefox History SQLite databases
adm-zip Bundling collected artefacts before exfiltration
sharp Resizing / compressing screenshots and image artefacts
socket.io-client Persistent bidirectional C2 channel
node-machine-id Stable per-host fingerprint for victim tracking

systemInfo.js calls os.networkInterfaces() for additional fingerprinting before the first beacon.

Why the Audio Capture Is the Standout Signal

Most npm RATs we triage in MEW stop at install-time secret theft. They read ~/.npmrc, read ~/.aws/credentials, scrape process.env, POST to a webhook, and exit. That fits a smash-and-grab economic model. Credentials have a short half-life, and the attacker needs to monetise quickly.

node-env-resolve is different in shape.

The persistence is set up to survive reboot. The agent runs detached and long-lived under wscript.exe. The kit it carries is for being there on a developer’s machine, not for grabbing and leaving: a microphone recorder, a keyboard/mouse driver, full browser-history ingest, screen capture, and an interactive Socket.IO channel back to a configurable C2.

Microphone capture in particular is the line this campaign crosses that most npm malware does not.

A developer workstation is, increasingly, also the workstation where developers take stand-ups, customer calls, design discussions, and on-call bridges. An implant that records the microphone is not really after the developer’s npm token. It is after what the developer says in front of the laptop.

That capability set, plus the lack of obfuscation and the absence of any flashy install-time exfiltration, reads as pre-positioning for targeted access rather than opportunistic credential theft.

We are not asserting attribution from this alone. We are noting the operational profile.

npm typosquatting attack - DevTab

The eight-day iteration cadence on node-env-resolve is the most operationally telling detail in the timeline.

This is not a fire-and-forget drop. The publisher is actively maintaining the dropper, which usually means one of two things. Either they are tuning it against test environments before broader deployment, or they already have install telemetry coming back and are responding to it.

The other five packages saw effectively no churn after their initial publish. That fits a “stage once, leave named” pattern for the supporting cluster while engineering effort focuses on the package that does the work.

Attribution: Small Clues, No Firm Verdict

Public OSINT signals are thin, and we will not stretch them. What is observable:

The Gmail handle tanvisoul9 partially resembles a South Asian given name, “Tanvi.” This is a weak signal. Gmail handles are not identity, and the trailing soul9 is generic. It is not safe to infer geography from an email local-part alone.

Code style is unremarkable Node.js: standard library calls such as os, child_process, spawn, and reg add. There is no obfuscation, no string rotation, and no anti-debug logic. The author is comfortable with Windows registry primitives and detached-child-process orchestration, but does not appear to be reaching for stealthier tradecraft they could plausibly be using.

Dependencies are entirely off the shelf and well known: screenshot-desktop, nut-js, better-sqlite3, and socket.io-client. There is no custom protocol, no rolled-from-scratch C2 stack, and no novel persistence trick beyond a textbook HKCU Run key. This is a competent integrator, not a tool author.

We did not find non-English strings, embedded comments, locale data, or compiler-output timezone fingerprints inside the published artefacts.

We checked for code overlap with prior campaigns we already track, including Shai-Hulud, Owlivion, Buildkite, and the recent heibai / claude-code-best Anthropic-CLI clone family. We found none: no shared C2 patterns, no shared file layouts, and no shared idioms.

What we would not say: that this is a state actor, a known group, or geographically tied to any specific country.

The operational profile is consistent with a small, moderately skilled team doing developer-workstation surveillance. It is likely financially motivated through downstream use of the access, but possibly also reconnaissance-as-a-service for a separate buyer.

Two indirect points support that read: the avoidance of attention-grabbing exfiltration mechanics that would burn the cluster fast, such as Telegram bots, oastify.com, or canarytokens, and the deliberately bland package names, which favour quiet long-tail installs over a brief high-volume spike.

Indicators of Compromise and Detection

Packages and Publisher
Field Value
npm publisher user0001
Publisher email tanvisoul9@gmail.com, unverified
Packages centralogger, dom-utils-lite, node-fetch-lite, connector-agent, node-gyp-runtime, node-env-resolve
Confirmed malicious node-env-resolve@1.0.7, node-env-resolve@1.0.8
Host Artefacts
Type Value
Persistence key HKCU\Software\Microsoft\Windows\CurrentVersion\Run
Persistence value Variable name; data of the form wscript.exe "<path>\\<stub>.vbs"
Install hook postinstall: node postinstall.js in the package manifest
Implant modules src/audioCapture.js, src/browserHistory.js, src/systemInfo.js
Staging directory INSTALL_DIR resolved outside the project’s node_modules; location set by postinstall.js at install time
Network
Type Value
C2 transport socket.io-client, persistent bidirectional channel
C2 endpoint Provided to the agent through the SERVER_URL environment variable; not hard-coded into published artefacts

Detection Notes

Two rules catch this family without needing the C2 endpoint.

First, flag postinstall scripts that perform an internal npm install into a path outside the package’s own directory. Any legitimate prebuild downloader, such as node-gyp rebuilds or prebuilt-binary downloaders, writes inside the package or into platform-standard cache paths with verified hashes. It does not write into a freshly created INSTALL_DIR elsewhere on disk.

Second, flag postinstall scripts that issue reg add HKCU\…\Run with wscript.exe as the launcher. This is essentially never legitimate from an npm package. Flag and quarantine it.

A third heuristic is useful for spotting the next sibling package before it is confirmed: a fresh npm publisher with no SCM verification, a Gmail email, no repository field, and multiple short, generic, infrastructure-sounding package names published within days of each other is, by itself, enough to warrant manual review.

Reported to the npm registry. We will update this post when the publisher account is removed.

sca-tools-software-composition-analysis-tools
Prioritize, remediate, and secure your software risks
7-day free trial
No credit card required

Secure your Software Development and Delivery

with Xygeni Product Suite