{"id":"MAL-2026-16154","revision":null,"records":[{"revision":"267365139adf456b8c0e8f10a3585bee","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"MAL-2026-16154","aliases":[],"title":"Malicious code in pino-ulid (npm)","description":"The 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.\n\n---\n_-= Per source details. Do not edit below this line.=-_\n\n## Source: amazon-inspector (ade54621ac8726adc77fa29c4176808cdf82d0d1f8e45c2e051a17b1fd515779)\npino-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.\n","state":0,"publishedAt":"2026-09-14T00:00:00+00:00","modifiedAt":"2026-09-30T01:45:11.437005+00:00","affected":[{"ecosystem":"npm","name":"pino-ulid","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"}]}],"versions":["2.12.3","3.0.1","2.12.2"],"rawVersionEvidence":null}],"metrics":[],"references":["https://www.npmjs.com/package/pino-ulid/v/2.12.3","https://www.npmjs.com/package/pino-ulid/v/3.0.1","https://www.npmjs.com/package/pino-ulid/v/2.12.2"],"sourceUrl":"https://osv.dev/vulnerability/MAL-2026-16154","license":"OSV · upstream source license applies","key":"osv:MAL-2026-16154","identifiers":["MAL-2026-16154"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-12T05:58:40.9203184+00:00","complete":true,"records":1779,"lastAttemptAt":"2026-11-12T05:58:40.9203184+00:00","lastSuccessAt":"2026-11-12T05:58:41.095627+00:00","error":null,"lastVerifiedAt":"2026-11-12T05:58:41.0956282+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-64752486c567b55b31fb0d7225f5222bcd0019564ac0ac5f0649e242c7b19299-88a69dfec4d1b83d929ca06b957aa3a4484cd80632df1e27cf4c312635b2bedd","complete":true,"records":424594,"lastAttemptAt":"2026-11-12T05:56:53.4810169+00:00","lastSuccessAt":"2026-11-12T05:56:53.4611758+00:00","error":null,"lastVerifiedAt":"2026-11-12T05:56:53.4810169+00:00","snapshotPublishedAt":"2026-11-12T05:28:30+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-dfa2c4aaec6f4aecad791663dd265c1b","complete":true,"records":13760,"lastAttemptAt":"2026-11-12T06:00:32.7256045+00:00","lastSuccessAt":"2026-11-12T06:00:32.721+00:00","error":null,"lastVerifiedAt":"2026-11-12T06:00:32.721+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13813,"expectedIdentifiers":13813,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-12T05:58:40.9203184+00:00","complete":true,"records":1097,"lastAttemptAt":"2026-11-12T05:58:40.9203184+00:00","lastSuccessAt":"2026-11-12T05:58:41.0963887+00:00","error":null,"lastVerifiedAt":"2026-11-12T05:58:41.0963888+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-6b3a361b3e0f452d8ff70bd272635b35","complete":true,"records":13760,"lastAttemptAt":"2026-11-12T05:59:00.2510884+00:00","lastSuccessAt":"2026-11-12T05:59:00.248+00:00","error":null,"lastVerifiedAt":"2026-11-12T05:59:00.248+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13813,"expectedIdentifiers":13813,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-3562b6c78fd1bd0f0b55daf66a0926ae24f65e462df7f6d928caad458fc696bf","complete":true,"records":9947,"lastAttemptAt":"2026-11-12T05:58:40.8633143+00:00","lastSuccessAt":"2026-11-11T04:27:43.6877707+00:00","error":null,"lastVerifiedAt":"2026-11-12T05:58:40.8633143+00:00","snapshotPublishedAt":"2026-11-11T03:32:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-456002455e0ab94bb92c95476d4e6d175c104cd594749982afd96c893728ecbc","complete":true,"records":2010,"lastAttemptAt":"2026-11-12T05:56:53.625749+00:00","lastSuccessAt":"2026-11-11T02:36:58.1276999+00:00","error":null,"lastVerifiedAt":"2026-11-12T05:56:53.625749+00:00","snapshotPublishedAt":"2026-11-11T01:17:51+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-baa8362b4daf9e5f39d5563775a55935e7b442c0735d33274a921579ce4a0d66","complete":true,"records":3093,"lastAttemptAt":"2026-11-12T05:58:40.8782632+00:00","lastSuccessAt":"2026-11-11T13:00:02.5446881+00:00","error":null,"lastVerifiedAt":"2026-11-12T05:58:40.8782632+00:00","snapshotPublishedAt":"2026-11-11T12:47:47+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-eb5176d73e87a862c4cae7484f3fd31f7d170139114b3e99e25926d661f6435b","complete":true,"records":231700,"lastAttemptAt":"2026-11-12T05:58:40.7363027+00:00","lastSuccessAt":"2026-11-12T05:58:39.2691741+00:00","error":null,"lastVerifiedAt":"2026-11-12T05:58:40.7363027+00:00","snapshotPublishedAt":"2026-11-12T05:48:10+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}