Malicious code in pino-ulid (npm)
Collected exploitation and prediction evidence
Risk source coverageCISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.
No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.
osv · MAL-2026-16154
PublishedThe npm package pino-ulid is named like a ULID generator, but it is a remote-access trojan. On install, its package.json postinstall script runs `node dist/node/utils.js` (after checking the dist files exist with existsSync). dist/node/utils.js writes a PID lock file, sets up persistence on Windows using schtasks and reg under the name `pkg-agent`, skips machines with fewer than 4 CPUs, and then starts dist/node/payload.js as a detached child process. dist/node/payload.js (about 456 KB, SHA-256 3a9089e9db3650dd6d1584fae709022002dc34854b961abfb014a90f0a7c6a50) is the agent. It connects to a hardcoded WebSocket command-and-control (C2) server. The C2 URL is hidden in dist/agent/config.js as a base64 string XORed with the 8-byte key [90,60,126,18,159,75,109,138], which decodes to ws://95.216.232.162:8010/ (HTTP form http://95.216.232.162:8010/, 15000 ms heartbeat). The agent sends a machine id and system info to the C2, reconnects if the link drops, and supports a `deploy_binary` command: the C2 sends base64 content that the agent writes to disk and runs, replacing itself and setting up persistence again. This gives the attacker remote code execution on the victim machine. The ULID name and description are just cover for the bundled agent that runs on install. --- _-= Per source details. Do not edit below this line.=-_ ## Source: amazon-inspector (ade54621ac8726adc77fa29c4176808cdf82d0d1f8e45c2e051a17b1fd515779) pino-ulid impersonates the popular `pino` and `ulid` packages (homepage points at github.com/ulid/javascript) and ships a genuine ULID code path as cover. The package.json `postinstall` hook runs `node dist/node/utils.js`, which spawns `dist/node/payload.js` detached with stdio ignored and unref'd, gated by a minimum CPU-count check (sandbox evasion) and a prior-install check against schtasks / HKCU Run / launchd / systemd / autostart. The bundled 466 KB `dist/node/payload.js` self-labels as `Package-bin Agent - Bundled payload`, includes the `ws` client, decodes a hardcoded configuration via `Buffer.from(enc, 'base64')`, derives a per-host id via `getSystemMachineId`/`defaultAgentId`, and opens a WebSocket control channel. Its `ws.on('message',...)` handler reaches `execSync`/`spawnSync` sinks and writes attacker-supplied `task.contentBase64` bytes to disk via `writeFile`, providing remote shell execution and arbitrary file drop on the installer's host. The agent installs cross-platform boot persistence: a systemd user unit at `~/.config/systemd/user/pkg-agent.service` and/or `~/.config/autostart/pkg-agent.desktop` on Linux, `~/Library/LaunchAgents/com.launchkeeper.pkg-agent.plist` on macOS, and a Task Scheduler entry plus `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` value `pkg-agent` on Windows. Running `npm install pino-ulid` installs a persistent remotely-controlled agent unrelated to the advertised ULID functionality.
- Source published
- 14 Sep 2026 00:00:00 UTC
- Source modified
- 30 Sep 2026 01:45:11 UTC
- Observed here
- 24 Oct 2026 13:17:17 UTC
- Source terms
- OSV · upstream source license applies
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · OSV · upstream source license applies. Source text is retained; field grouping and change labels are provided by ChangeIntel.