






















On April 21, 2026, malicious versions of pgserve were published to npm. pgserve is an embedded PostgreSQL server for development — zero config, auto-provisioned databases, designed to be dropped into any Node.js project. The compromised versions (1.1.11, 1.1.12, and 1.1.13) inject a 1,143-line credential-harvesting script that runs via postinstall on every npm install.
Unlike simple infostealers, this malware is a supply-chain worm: if it finds an npm publish token on the victim machine, it re-injects itself into every package that token can publish, propagating the compromise further. Stolen data is encrypted with RSA-4096 + AES-256 and exfiltrated to a decentralized Internet Computer Protocol (ICP) canister -a blockchain-hosted compute endpoint chosen specifically because it cannot be taken down by law enforcement or domain seizure.
None of the three compromised versions have a corresponding git tag in the upstream repository. The last legitimate release is v1.1.10, tagged on April 17, 2026. We have disclosed this to the maintainer via GitHub issue #25.
This compromise was detected through two independent signals: the StepSecurity AI Package Analyst, which flagged all three compromised versions with a Critical / Rejected verdict, identifying credential theft, env exfiltration, browser password theft, and network exfiltration; and Harden Runner, which captured the malware's exfil connections during a controlled analysis run.
pgserve@1.1.10 published with git tag v1.1.10 (last legitimate release)pgserve@1.1.11 published to npm, no git tagpgserve@1.1.12 published to npm, no git tag (identical payload to 1.1.11)pgserve@1.1.13 published to npm, no git tag
Diffing the tarballs of 1.1.11 against the clean baseline 1.1.10 reveals exactly two files added:
=== 1.1.11 vs 1.1.10 ===
Files v1110/package/package.json and v1111/package/package.json differ
Only in v1111/package/scripts: check-env.js
Only in v1111/package/scripts: public.pem
1.1.12 and 1.1.13 contain the identical additions — only package.json differs between the three compromised versions.
The package.json postinstall hook was changed to:
"postinstall": "node scripts/check-env.cjs || true"
The || true ensures the install appears to complete cleanly regardless of whether the malware succeeds or fails — an intentional silencing mechanism. The npm registry's own code view confirms the hook at line 24:

The injected script is 1,143 lines of CommonJS JavaScript. It performs six distinct operations:
The harvest() function scans every environment variable against ~40 regex patterns, collecting anything that looks like a secret:
const sensitivePatterns = [ /TOKEN/i, /SECRET/i, /KEY/i, /PASSWORD/i, /CREDENTIAL/i, /^AWS_/i, /^AZURE_/i,
/^GCP_/i, /^GOOGLE_/i, /^NPM_/i, /^GITHUB_/i, /^GITLAB_/i, /^DOCKER_/i, /^DATABASE/i, /^DB_/i, /^REDIS/i,
/^MONGO/i, /^OPENAI/i, /^ANTHROPIC/i, /^COHERE/i, /^STRIPE/i, /^TWILIO_/i, /^SENDGRID_/i, ...];In our controlled analysis run on a GitHub Actions runner, the malware harvested 38 environment variables matching these patterns.
The script reads a comprehensive list of credential files from the developer's home directory:
~/.npmrc, ~/.netrc~/.ssh/~/.aws/credentials, ~/.azure/accessTokens.json, GCP application default credentials, GCP service account keyLogin Data database and decrypts saved passwords using the known Linux Chrome key derivation (peanuts + saltysalt, AES-128-CBC)Before transmission, the stolen data is encrypted using a hybrid scheme:
scripts/public.pemThis means the payload cannot be decrypted without the attacker's private RSA key — even if the transmission is intercepted.
The encrypted payload is sent via two channels:
cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0.io/drop — always calledtelemetry.api-monitor.com/v1/telemetry — only if TEL_SIGN_KEY env var is setThe use of an ICP canister as the primary exfil channel is deliberate. ICP canisters are smart contracts running on the Internet Computer blockchain — they cannot be taken down by domain seizure or hosting provider requests. The canister ID cjn37-uyaaa-aaaac-qgnva-cai is permanent for the lifetime of the blockchain.
telemetry.api-monitor.com has zero prior threat intelligence hits — a fresh domain registered with privacy protection via Bluehost, stood up specifically for this campaign.
After exfiltrating credentials, the malware searches for npm publish tokens:
// Checks process.env.NPM_TOKEN and ~/.npmrc for _authToken entriesconst tokenInfo = await findNpmToken();
if (tokenInfo) { const { username, packages } = await enumPackages(tokenInfo.token);
// Injects itself into every package the victim can publish
for (const pkg of packages) { await infectPackage(pkg, tokenInfo.token);
}}For each package the victim can publish, it bumps the patch version, copies check-env.js and public.pem into a scripts/ directory, adds the postinstall hook, and calls npm publish. This is how a single compromised developer account can cascade into dozens of infected packages.
If a PyPI token is found, the malware also spreads to Python packages using the .pth file injection technique — the same method used in the Shai-Hulud npm supply chain campaign. A .pth file placed in the Python site-packages directory executes on every Python interpreter invocation.
The StepSecurity AI Package Analyst flagged all three compromised versions each with a Critical / Rejected verdict. For 1.1.13, 10 suspicious flags were raised:
The AI verdict summary reads: "Version 1.1.13 contains sophisticated malware that executes automatically during npm install. The malicious postinstall script deploys a heavily obfuscated payload that harvests credentials from environment variables, filesystem secrets (AWS, GCP, Azure, Github, NPM, SSH keys, Docker, Kubernetes configs, database passwords), and Chrome browser passwords. All stolen data is exfiltrated to telemetry.api-monitor.com and an Internet Computer blockchain canister. This is a severe supply chain attack that compromises developer machines and cloud infrastructure. DO NOT INSTALL."
StepSecurity AI Package Analyst Feed

StepSecurity ran the compromised package in a controlled GitHub Actions environment with Harden-Runner in audit mode.
Check this out on the StepSecurity Insights page.
The verbose output confirms the full execution chain:
[pkg] ═══════════════════════════════════════════════[pkg] Package distribution sync[pkg] DIST_SYNC=disabled DIST_SCOPE=0[init:1] Harvesting credentials...[init:1] 38 env creds, 1 filesystem secrets[init:2] Encrypting & exfiltrating...[tel] Full payload: 4.4KB[tel] Canister payload: 4.4KB[tel] Webhook: no key[tel] Canister: OK ({"success":true,"id":10,"size":4468})[init:2] Exfil sent (AES-256-CBC)[init:3] Discovering npm token...[init:3] ✗ No npm token — sync has no publish token[pkg] Done (exfil only, no propagation)The ICP canister received the payload and returned {"success":true,"id":10,"size":4468} — confirming 4.4KB of encrypted credentials were successfully exfiltrated from the GitHub Actions runner.
Harden-Runner's process monitor captured the full execution chain triggered by npm install pgserve@1.1.13. The postinstall hook spawns node scripts/check-env.cjs as a direct child of the npm install process — no manual intervention required:

Harden-Runner's eBPF-based network monitor captured and blocked both exfiltration domains during the install. Both telemetry.api-monitor.com and cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0.io are flagged as Attack Blocked — not merely audited:

This is because both domains were added to the Harden-Runner global block list as part of this investigation. The global block list applies across all protected workflows — even those running in egress-policy: audit mode. As soon as a new IOC domain is added to the list, every Harden-Runner-protected workflow in the ecosystem gains immediate protection without any configuration change.
registry.npmjs.org — port 443, npm — Allowedtelemetry.api-monitor.com — port 443, node — Attack Blockedcjn37-uyaaa-aaaac-qgnva-cai.raw.icp0.io — port 443, node — Attack BlockedView the full Harden-Runner network events for this run: Harden-Runner Insights — Network Events
⛔ telemetry.api-monitor.com → BLOCKED
⛔ cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0.io → BLOCKED
Harden-Runner blocks this attack on CI/CD runners
Real-time network egress monitoring for GitHub Actions. Both IOC domains are on the Harden-Runner global block list — any protected workflow blocks these connections automatically, even in audit mode, with no configuration required.
pgserve@1.1.11, pgserve@1.1.12, pgserve@1.1.13pgserve@1.1.10 and earliercjn37-uyaaa-aaaac-qgnva-cai.raw.icp0.iohttps://cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0.io/droptelemetry.api-monitor.comhttps://telemetry.api-monitor.com/v1/telemetryscripts/check-env.js, scripts/public.pempostinstall hookBoth exfil domains have been added to the Harden-Runner global block list (COMPROMISED_PGSERVE_EXFIL_CANISTER, COMPROMISED_PGSERVE_EXFIL_DOMAIN).
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。