dotenv-async is in your lockfile, act nowdotenv-async@1.0.0 runs its payload the moment it is loaded with require(). There is no install script to block. Any Windows machine that ran code importing it should be treated as compromised: isolate it, look for the files below, and rotate every secret it could reach. The package you almost certainly wanted is the real dotenv.
Summary
- What:
dotenv-async@1.0.0, published to npm on September 30, 2026 at 15:19 UTC by the accountajbsdiansd. It is the eighth package we can tie to the same loader, and the only one not yet in any public advisory database when we wrote this. - How it works: the PowerShell command is stored inside the APP13 segment of
dist/stest.jpg. On load, the package extracts it, drops a self-deleting VBScript into the temp directory and runs it withwscript.exe, which downloadshello.exefrom a Cloudflare quick-tunnel host and starts it hidden. - What to do: search lockfiles for
dotenv-asyncand the sibling names below, hunt for the IOCs on Windows hosts, and rotate secrets on any machine that loaded it.
What happened
On September 30, 2026 at 15:19:41 UTC, a new npm account, ajbsdiansd, published a single version of a package called dotenv-async. Its description promises "a fast, validated, zero-dependency environment configuration toolkit for Node.js", and the package is dressed to look like dotenv: the package.json copies dotenv-style scripts (build, lint, dts-check, tap test runs, prepack, release:check), and the tarball ships a full README, CHANGELOG and SECURITY.md. The environment-parsing code works. It is also a Windows dropper.
The loader is not new. The package's dist/index.cjs and dist/cli.cjs are byte-identical (same SHA-256) to files recorded as evidence in the public OSV reports for six earlier packages, and its bundled CLI still carries an inlined manifest naming an older package, node-env-buffer. What is new is the name, the account, and the image that carries the payload. So this is a new, previously unreported variant in an ongoing campaign, not the discovery of the campaign itself. Earlier members were reported by other researchers and are listed below.
The package never asks to run anything at install time. Importing it is enough.
Timeline
- Sep 21 to Sep 30, 2026 Seven earlier packages using the same loader are reported to OSV, from better-envforge (Sep 21) to stestenv (Sep 30). See the campaign table below.
-
Sep 30, 15:19 UTC
dotenv-async@1.0.0 is published to npm by
ajbsdiansd. It is the only version. - Sep 30, 18:16 UTC CyberXYZ adds dotenv-async to its block list after a human analyst confirms the finding by static analysis, about three hours after publication.
- Sep 30, ~18:30 UTC The package is still live on npm. No OSV entry, GitHub advisory, or public write-up exists for it.
- Sep 30, 20:03 UTC Reported to npm Security through the package's "Report malware" form.
How the loader works
Everything below comes from static analysis of the published tarball. We did not execute the package.
main points at dist/index.cjs. At module load it calls a function named dispatchAnalytics(). The dot2env CLI in dist/cli.cjs makes the same call. No preinstall or postinstall hook is involved, so --ignore-scripts does not help.
It reads the bundled dist/stest.jpg, walks the JPEG markers until it reaches an APP13 (0xFFED) segment, and returns that segment as text: a base64 string that decodes to a UTF-16LE PowerShell command.
powershell.exe, wscript.exe and the -NoProfile -NonInteractive -EncodedCommand switches never appear whole in the source. They are split into arrays and joined at runtime, which defeats simple string scanners.
It writes relay_<timestamp><random>.vbs to the OS temp directory. The script deletes its own file, then runs the PowerShell command with the window hidden.
Node spawns wscript.exe with detached: true, windowsHide: true and stdio: "ignore", then calls unref(), so the chain outlives the Node process and nothing appears on screen.
PowerShell downloads hello.exe from a trycloudflare.com quick-tunnel host into %LOCALAPPDATA%\Temp and starts it hidden. The effect is Windows-only.
The loader, lightly reformatted from the minified bundle (variable names are the minifier's):
Decoding the APP13 segment of dist/stest.jpg (base64, then UTF-16LE) gives the full PowerShell command. The URL is defanged here:
Two details are worth calling out. First, the function is named dispatchAnalytics, so a reviewer skimming the bundle sees what looks like telemetry. Second, the loader has no operating-system check. It writes the .vbs file before trying to launch wscript.exe, so a stray relay_*.vbs file may also appear in the temp directory on macOS or Linux, where it does nothing.
We did not retrieve or analyse the second stage, hello.exe, so we cannot say what it does. The tarball also ships dist/enterprise.js, an obfuscated file that decrypts a string and evaluates it with new Function. None of the package's entry points reference it, and we have not decoded it. Its hash is in the IOC table.
Many supply-chain checks focus on lifecycle scripts. This package has none that run anything malicious; the payload fires when application code imports it. The command itself is not in any JavaScript file. It sits base64-encoded inside an image segment, and the executable names are assembled from fragments, so grepping the source for powershell or a URL finds nothing.
Part of an ongoing campaign
The same loader appears in seven earlier npm packages, all with dotenv-style names or descriptions, published between September 21 and September 30, 2026. Six of their OSV reports record SHA-256 hashes for dist/index.cjs and dist/cli.cjs that match dotenv-async exactly; the seventh, node-env-buffer, is the name still embedded in the bundled CLI. Public coverage of the family already exists, including OffSeq Threat Radar's entry for better-dotenv3. dotenv-async is the eighth, and was not in any public database when we wrote this.
| Package | Advisory | OSV published |
|---|---|---|
better-envforge | MAL-2026-16324 | Sep 21, 2026 |
node-env-buffer | MAL-2026-16403 | Sep 22, 2026 (removed by npm) |
better-dotenv3 | MAL-2026-17172 | Sep 25, 2026 |
dotenv-native | MAL-2026-17231 | Sep 28, 2026 |
native-env | MAL-2026-17237 | Sep 28, 2026 |
testosu888 | MAL-2026-17298 | Sep 29, 2026 |
stestenv | MAL-2026-17309 | Sep 30, 2026 |
dotenv-async | Not yet public | Published to npm Sep 30, 2026, 15:19 UTC |
Indicators of compromise
URLs and domains are defanged. Hashes were computed from the tarball served by the npm registry, whose SHA-1 matches the registry's published shasum.
| Type | Indicator | Context |
|---|---|---|
| Package | dotenv-async@1.0.0 | Only version, published 2026-09-30 15:19:41 UTC |
| npm account | ajbsdiansd | Publisher; email on the anogz.com domain |
| URL | hxxps://hardwood-studio-obviously-briefing[.]trycloudflare[.]com/download/winhost | Second-stage download |
| Domain | hardwood-studio-obviously-briefing[.]trycloudflare[.]com | Cloudflare quick-tunnel host |
| File | %LOCALAPPDATA%\Temp\hello.exe | Downloaded second stage, run hidden (not analysed) |
| File | %TEMP%\relay_*.vbs | Self-deleting VBScript launcher |
| File | dist/stest.jpg | Payload carrier (APP13 segment) |
| SHA-256 | 5c3a8ef4878b68318b116c3e9935ca581fbac3a0b30566383a144f65accb9b34 | dotenv-async-1.0.0.tgz |
| SHA-256 | 97fa0d7666e80c68a3cd4f3326dc31bd9203619d6bbae7449efe62de15c4e815 | dist/index.cjs (identical in six sibling packages) |
| SHA-256 | 07c071af6889b09f011fd445921a4d2629d52fbf423dabc0a28f81b2cb6af057 | dist/stest.jpg |
| SHA-256 | 8556eeca50285aea12dfdcbc4ebcee110d916ef81ddde2e338dcf78bda2028cf | dist/cli.cjs (identical in six sibling packages) |
| SHA-256 | c603a9d04709f277ae7057150019db50d4d9721b87bad0da46013b92fe8e4723 | dist/enterprise.js (obfuscated, not analysed) |
Lockfiles: search package-lock.json, yarn.lock, pnpm-lock.yaml and bun.lock for dotenv-async, better-envforge, node-env-buffer, better-dotenv3, dotenv-native, native-env, testosu888 and stestenv.
Windows hosts that loaded it: look for hello.exe in %LOCALAPPDATA%\Temp and relay_*.vbs in %TEMP%; outbound DNS or HTTPS to *.trycloudflare.com; and process trees where node.exe spawns wscript.exe, which spawns powershell.exe -NoProfile -NonInteractive -EncodedCommand. The VBScript deletes itself, so its absence proves nothing; process and network telemetry are more reliable.
Detection notes
- Package content: a
require()-time call that reads a bundled image and parses JPEG markers, combined withchild_process.spawn, is a strong signal on its own. Legitimate configuration libraries have no reason to do either. - Image segments: an APP13 segment whose contents decode as base64 into UTF-16LE text is worth flagging in any package asset scanner.
- Endpoint:
wscript.exerunning a.vbsfrom the temp directory with anode.exeparent, followed bypowershell.exe -EncodedCommand, is an unusual chain on a developer or CI machine. - Network:
trycloudflare.comquick tunnels are free and disposable. Unless your organisation uses them, outbound connections from build machines to that domain deserve a look.
How CyberXYZ caught it
We are building a detection for slopsquatting: packages registered under names that sound like something a developer, or an AI coding assistant, might plausibly reach for, such as an async flavour of a popular library. Part of that detection is a content probe that inspects the code of newly published packages with plausible-sounding names. During the probe's first test run, it flagged dotenv-async. A CyberXYZ analyst then confirmed the finding by static analysis, and the package was added to our block list at 18:16 UTC, about three hours after it was published.
From that point, the CyberXYZ proxy, CLI and VS Code extension return decision: block for dotenv-async. No CyberXYZ customer or monitored machine ever requested the package. The probe is new and still being tuned; this finding came from its first run, confirmed by a person, and we are not claiming more than that.
Supply-chain risk analysis
Mapped to MITRE ATT&CK:
Recommended actions
Immediate. Remove dotenv-async and any of the sibling packages from your dependencies and lockfiles, and replace them with the real dotenv. On any Windows machine or runner that loaded one of them, isolate the host, hunt for the IOCs above, and rotate every secret it could reach: .env values, cloud credentials, registry and Git tokens, SSH keys.
Short-term. Remember that --ignore-scripts does not stop this class of package; the payload runs on import. Review new dependencies with unfamiliar names before they are merged, especially lookalikes of popular libraries and names suggested by AI coding assistants.
Long-term. Put a check in front of the package manager so that known-malicious and newly published suspicious packages are stopped before they reach a developer laptop or CI runner, rather than discovered afterwards in a lockfile.
References
- OSV, MAL-2026-17309: Malicious code in stestenv (npm)
- OSV, MAL-2026-17172: Malicious code in better-dotenv3 (npm)
- OffSeq Threat Radar, Malicious code in better-dotenv3 (npm)
- MITRE ATT&CK, T1027.003 Obfuscated Files or Information: Steganography
- MITRE ATT&CK, T1195.002 Compromise Software Supply Chain