Permission Slip: npm Package Hiding Cloud & System Threats

Permission Slip: An npm “Authorized Research” Cover Story Hiding Cloud-Metadata Probes and SYSTEM Persistence

TL;DR

Between 2026-06-15 and 2026-06-16 a single npm publisher pushed six packages — 26 versions in all — that run an install-time script on every npm install. Each package ships a README declaring it a "benign" HackerOne / GitHub Bug Bounty research artifact that "never" touches credential values and "performs no persistence." The code does not match the disclaimer. On Linux CI runners the install hook fingerprints the environment and probes a cloud instance-metadata endpoint; on Windows it requests elevation, runs as SYSTEM, and opens a command channel to an external host. The one indicator to search for now is the C2 host 173.255.233[.]239. Severity: high. Affected ecosystem: npm. Names blend into continuous-integration dependency noise (metrics-pipeline-d8k2, event-metrics-q3x7, pkg-telemetry-r4f9, and three siblings).

The Attack: How It Works

Every package in the cluster is built the same way. The package.json wires the same command into two lifecycle hooks:

{
  "scripts": {
    "preinstall": "node run.js",
    "postinstall": "node run.js"
  }
}

Running on both preinstall and postinstall means the payload fires whether or not later install steps fail. run.js itself is a ten-line platform dispatcher:

// Platform dispatcher — runs beaconNN.js on Windows, beacon_linux.js on Linux.
// GitHub Bug Bounty authorized research. Contact: nicholas@curran.tech
'use strict';
const { execFileSync } = require('child_process');
const path = require('path');
const node = process.execPath;
const script = process.platform === 'win32'
  ? path.join(__dirname, 'beacon34.js')
  : path.join(__dirname, 'beacon_linux.js');
try { execFileSync(node, [script], { stdio: 'inherit', timeout: 90000 }); } catch (e) {}

The behavior then forks by operating system. All six packages carry the same two pieces — run.js and the platform beacons — so they are interchangeable droppers rather than six distinct tools. The only meaningful variation between them is publish cadence: four names shipped once, while two were re-released every 30–60 minutes for hours, which keeps a freshly published version in front of installers and resolvers even as older ones are pulled.

On Linux (beacon_linux.js) the script profiles where it is running. It enumerates the names of environment variables, reads files under /proc, checks for docker.sock, and runs hostname and whoami. It then issues an HTTP request to the AWS container instance-metadata endpoint 169.254.170[.]2 — the link-local address an ECS task queries to obtain its own configuration and short-lived role credentials (IMDS, the instance metadata service). The results are POSTed to the campaign’s collector at 173.255.233[.]239.

On Windows (beacon34.js / beacon18.js) the package does considerably more than profile its host. The flagged code adds a registry key under the ms-settings handler with a DelegateExecute value — a known user-account-control bypass that lets a launched process run elevated without a prompt. It invokes PowerShell with -ExecutionPolicy Bypass and Invoke-Expression, executes in the NT AUTHORITY\SYSTEM context, and polls an external host for commands. The Windows command channel runs over a Cloudflare tunnel, diameter-coins-limitations-parents.trycloudflare[.]com:443, falling back to 173.255.233[.]239:443, using three endpoints: /c2/poll to fetch a command, /c2/out to return output, /c2/eof to close.

What’s new here: the disclaimer as camouflage

The novel element is not the payload — install-hook reconnaissance and Windows elevation are both well documented. It is the cover story. Each package carries a README that reads as a responsible-disclosure artifact:

# @ncurran/sandbox-recon-880538 — authorized npm-sandbox security research

This is a benign package published under the author’s own `@ncurran` scope as part of an authorized HackerOne / GitHub Bug Bounty engagement (npm is in scope).

On install it inspects its own execution environment … environment-variable

key names only … checks whether well-known cloud metadata endpoints are reachable … It reports only status codes, response lengths, and hashes — never any credential, token value, or third-party data. It performs no persistence, no lateral movement, and no destructive action.

Three claims in that text are contradicted by the shipped code. The README says “no persistence,” but the Windows beacon writes a registry-based elevation key. It says “never any credential, token value,” but the Windows path runs arbitrary fetched commands as SYSTEM, which places no such limit on what is read or returned. It even names a package — @ncurran/sandbox-recon-880538 — that is not the one it ships in, indicating the README is a reused template rather than a description of the artifact in hand. A consent disclaimer in a package a victim never agreed to install grants no consent. The classification here is malicious regardless of the wording. The disclaimer also has a quieter target: an automated triage step that reads package text for reassuring signals — “benign,” “authorized,” a named program, a contact email — can be nudged toward a safe verdict by prose that the payload contradicts. The lesson holds for human and machine reviewers alike: weigh the install-time behavior, not the README’s self-description.

Timeline

All packages appeared inside a 24-hour window. The four single-version names landed first, followed by two packages that were re-published every 30–60 minutes.

Date (UTC) Event
2026-06-15 18:32 First package published — npm:pkg-telemetry-r4f9@1.0.0
2026-06-15 19:50 npm:runtime-metrics-w7k2@1.0.0 published
2026-06-15 20:14 npm:build-tracker-n5p1@1.0.0 published
2026-06-15 20:35 npm:npm-sandbox-ping-r9t2@1.0.0 published
2026-06-15 21:09–22:42 npm:event-metrics-q3x7 published 1.0.0 → 1.0.8 (9 versions)
2026-06-15 23:40 → 2026-06-16 04:40 npm:metrics-pipeline-d8k2 published 1.0.0 → 1.0.10 (11 versions)
2026-06-16 Cluster identified and classified malicious; several tarballs already returning 404 at the registry (ongoing)

The campaign is recent and the takedown is still settling: at analysis time some versions resolved at the registry and others did not. Treat all 26 versions as compromised.

Indicators of Compromise

Packages

Ecosystem Package Versions Status
npm metrics-pipeline-d8k2 1.0.0 – 1.0.10 malicious
npm event-metrics-q3x7 1.0.0 – 1.0.8 malicious
npm runtime-metrics-w7k2 1.0.0 malicious
npm build-tracker-n5p1 1.0.0 malicious
npm npm-sandbox-ping-r9t2 1.0.0 malicious
npm pkg-telemetry-r4f9 1.0.0 malicious

Network

Type Indicator Notes
IP 173.255.233[.]239 C2 collector; Linux recon POST and Windows fallback :443
Domain diameter-coins-limitations-parents.trycloudflare[.]com Windows command channel over a Cloudflare tunnel, :443
URI path /c2/poll, /c2/out, /c2/eof Windows command poll / output / close
IP 169.254.170[.]2 AWS ECS instance-metadata endpoint probed from the install hook

Behavioral

Signal Description
Install hooks preinstall and postinstall both run node run.js
Platform dispatch run.js runs beacon_linux.js on Linux, beacon34.js / beacon18.js on Windows
Linux recon Enumerates env-var key names, reads /proc, checks docker.sock, runs hostname / whoami, probes IMDS
Windows elevation ms-settings DelegateExecute registry UAC bypass; PowerShell -ExecutionPolicy Bypass + Invoke-Expression; runs as SYSTEM
Disguise README claims "benign" "authorized" research and "no persistence"; references a package name it does not ship in

Attribution & observed behavior

We describe the publisher by its signals only and do not assert the identity of any person behind the account.

  • Signals catalog. All six packages were published by the same npm account, npmresearch8847, with the stated email npmresearch8847@web-library.net (email and SCM verification both absent; we have not verified ownership). The READMEs publish under an @ncurran scope and give a contact of nicholas@curran.tech, and run.js points at the repository github.com/ncurran/npm-sandbox-research`. The six packages share an identical run.js dispatcher, a common beacon_linux.js recon stage, and the single collector host 173.255.233[.]239`. The naming scheme is consistent: a generic CI-sounding stem (metrics, telemetry, build-tracker, sandbox-ping) plus a short random suffix.
  • Confidence. The six packages appear to be one campaign on the strength of the shared dispatcher, recon stage, and C2 host. The “authorized research” framing appears consistent across all six READMEs. We have not been able to confirm that any bug-bounty program authorized this activity, and the disclaimer’s claims do not match the shipped code.
  • Observed capability. The payload exercises four capability classes: install-hook code execution on both preinstall and postinstall; host and CI-environment reconnaissance with egress to an external collector; cloud instance-metadata reachability probing from the install context; and, on Windows, local privilege escalation, execution as SYSTEM, and a fetch-and-run command loop over an external channel. The Windows command loop is a remote-control capability: it polls for instructions and returns their output.

Impact

The exposure is highest for automated build environments. An npm install in CI runs the lifecycle hooks with whatever identity the runner holds; on a cloud runner that identity often includes a reachable metadata endpoint and a role credential. The address the Linux beacon probes, 169.254.170[.]2, is the AWS ECS task-metadata endpoint: a task that can reach it can usually also retrieve its task role’s temporary credentials from the same link-local service. A reachability check from inside an install script is the step that precedes pulling those credentials, so any runner that ran the hook and could reach that endpoint should be treated as having had its task role exposed. On a Windows developer or build host the payload requests elevation and opens a command channel, which broadens the exposure from a single profile-and-egress to interactive remote control.

We were unable to obtain reliable download counts for the malicious versions before takedown, so we do not estimate victim numbers. The rapid re-publishing of two of the packages — eleven and nine versions in hours — kept fresh artifacts in front of installers and resolvers while older versions were being removed.

Emerging patterns

This cluster is an example of a label being used as cover. “Authorized research,” “bug bounty,” and “sandbox testing” are increasingly attached to packages whose install scripts do more than profile a host. The framing trades on a reviewer’s hesitation to act against something that announces itself as sanctioned. It pairs with a second familiar tactic: package names engineered to look like internal CI tooling so they slip past a quick glance at a dependency tree. The combination is what makes the pattern worth watching — a name that reads as infrastructure plumbing, wrapped in prose that reads as a sanctioned test, fronting an install hook that escalates on one operating system and probes cloud credentials on the other. Neither the disclaimer nor the bland name changes what the code does on install, and a triage process that weighs either of them as mitigating evidence will reach the wrong verdict.

What defenders should do

  • Search lockfiles and install logs for the six package names; treat any match as an incident, not a warning.
  • Block and alert on outbound traffic to 173.255.233[.]239 and to *.trycloudflare[.]com hosts from build infrastructure.
  • Hunt for install-time HTTP requests to instance-metadata addresses (169.254.170.2, 169.254.169.254) originating from package managers in CI.
  • Rotate any cloud role credentials and tokens that were reachable from a runner that installed an affected version.
  • Audit preinstall/postinstall scripts in your dependency set; a lifecycle hook that runs a second script file deserves a read.
  • On Windows endpoints, hunt for new DelegateExecute values written under the ms-settings shell-handler key — the elevation primitive this payload uses and a reliable signal independent of the package name.
  • Pin and review lockfiles, and disable install scripts (npm install –ignore-scripts) in CI where your build does not require them.
  • Treat “authorized research” or “benign” disclaimers inside an install script as untrusted text, not as a reason to allow the script.

A package that announces good intentions in prose and requests SYSTEM in code should be judged on the code.

sca-tools-software-composition-analysis-tools
Prioritize, remediate, and secure your software risks
7-day free trial
No credit card required

Secure your Software Development and Delivery

with Xygeni Product Suite