Suspicious PowerShell from npm Package Install
Detects PowerShell launched with an encoded command or a download cradle whose process ancestry includes a Node.js
npm package execution (npx-cli.js with a scoped or versioned package). Malicious npm packages and supply-chain
compromises commonly run this from install or postinstall scripts to fetch and execute a second stage.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2026/09/22"
3integration = ["endpoint"]
4maturity = "production"
5min_stack_comments = "ES|QL inline stats became generally available in 9.3.0 and MV_INTERSECTION is in preview since 9.3."
6min_stack_version = "9.3.0"
7updated_date = "2026/09/22"
8
9[rule]
10author = ["Elastic"]
11description = """
12Detects PowerShell launched with an encoded command or a download cradle whose process ancestry includes a Node.js
13npm package execution (`npx-cli.js` with a scoped or versioned package). Malicious npm packages and supply-chain
14compromises commonly run this from install or postinstall scripts to fetch and execute a second stage.
15"""
16from = "now-9m"
17language = "esql"
18license = "Elastic License v2"
19name = "Suspicious PowerShell from npm Package Install"
20note = """## Triage and analysis
21
22### Investigating Suspicious PowerShell from npm Package Install
23
24PowerShell with an encoded command or a download cradle (`DownloadString`, `Invoke-WebRequest`, `Invoke-Expression`,
25BITS) ran in a process tree that includes Node.js executing an npm package via `npx-cli.js`. That pattern is common
26in malicious npm packages and compromised dependencies.
27
28### Possible investigation steps
29
30- Review `all_pkg_cmdline` for the `npx-cli.js` invocation and identify the package name, scope, and version.
31- Inspect `process.command_line` and whether `is_encoded_powershell` or `is_download_cradle` is true. Decode any
32 `-EncodedCommand` payload and extract URLs, file paths, and follow-on commands.
33- Correlate the same `host.id` and `user.name` for new files, outbound connections, and additional child processes
34 from the Node.js or PowerShell tree.
35- Check recent npm installs, `package.json` lifecycle scripts, and lockfile changes on the host for the package in
36 `all_pkg_cmdline`.
37
38### False positive analysis
39
40- Some legitimate packages download helpers or build tools with PowerShell during install. Confirm the package,
41 publisher, and destination URL before excluding.
42- Scope exceptions to a known package command line, host, or user. Do not broadly exclude `npx` or `powershell.exe`.
43
44### Response and remediation
45
46- If malicious, isolate the host, stop the Node.js and PowerShell process tree, and remove the package and any
47 dropped files.
48- Rotate credentials and tokens available to that user or build environment, including npm tokens and cloud keys.
49- Block the package and related domains, and hunt for the same `all_pkg_cmdline` and PowerShell command line on
50 other hosts.
51"""
52references = ["https://www.elastic.co/blog/shai-hulud-worm-npm-supply-chain-compromise"]
53risk_score = 73
54rule_id = "c4e8a1d7-2b6f-4e93-8a15-7d0c3f9b6e42"
55severity = "high"
56tags = [
57 "Domain: Endpoint",
58 "OS: Windows",
59 "Platform: Windows",
60 "Use Case: Threat Detection",
61 "Tactic: Execution",
62 "Tactic: Initial Access",
63 "Tactic: Defense Evasion",
64 "Tactic: Command and Control",
65 "Data Source: Elastic Defend",
66 "Resources: Investigation Guide",
67 "Rule Type: ES|QL",
68 "Threat: Supply Chain",
69 "Threat: Script-Based Execution",
70]
71timestamp_override = "event.ingested"
72type = "esql"
73
74query = '''
75FROM logs-endpoint.events.process-* METADATA _id, _version, _index
76
77| EVAL is_pkg_install = CASE(
78 // npm
79 process.parent.name == "node.exe" AND (
80 process.parent.command_line LIKE "*npx-cli.js*" OR
81 process.parent.command_line LIKE "*npm-cli.js*" OR
82 process.parent.command_line LIKE """*\\npm-cache\\_npx\\*"""
83 ), true,
84 false
85 )
86
87| EVAL cmdline_lower = TO_LOWER(process.command_line)
88
89| EVAL is_encoded_powershell = CASE(
90 TO_LOWER(process.name) == "powershell.exe" AND (
91 cmdline_lower LIKE "* -enc*" OR
92 cmdline_lower LIKE "* -en *" OR
93 cmdline_lower LIKE "* -ec *" OR
94 cmdline_lower LIKE "* -ec:*" OR
95 cmdline_lower LIKE "*frombase64*"
96 ), true, false
97 )
98
99// plain -Command / dot-sourced / no-flag download cradles that skip -EncodedCommand entirely
100| EVAL is_download_cradle = CASE(
101 process.name == "powershell.exe" AND (
102 cmdline_lower LIKE "*downloadstring*" OR
103 cmdline_lower LIKE "*downloadfile*" OR
104 cmdline_lower LIKE "*downloaddata*" OR
105 cmdline_lower LIKE "*net.webclient*" OR
106 cmdline_lower LIKE "*new-object*webclient*" OR
107 cmdline_lower LIKE "*invoke-webrequest*" OR
108 cmdline_lower LIKE "* iwr *" OR
109 cmdline_lower LIKE "* iwr(*" OR
110 cmdline_lower LIKE "*invoke-restmethod*" OR
111 cmdline_lower LIKE "* irm *" OR
112 cmdline_lower LIKE "* irm(*" OR
113 cmdline_lower LIKE "*invoke-expression*" OR
114 cmdline_lower LIKE "* iex *" OR
115 cmdline_lower LIKE "* iex(*" OR
116 cmdline_lower LIKE "*start-bitstransfer*" OR
117 cmdline_lower LIKE "*bitstransfer*"
118 ), true, false
119 )
120
121| EVAL is_susp_powershell = CASE(is_encoded_powershell OR is_download_cradle, true, false)
122
123| WHERE process.Ext.ancestry IS NOT NULL AND (is_susp_powershell OR is_pkg_install)
124
125// Capture entity_ids for package install parent processes
126| EVAL all_entity_id = CASE(is_pkg_install, process.parent.entity_id, "null")
127
128// Collect all package install entity_ids globally
129| INLINE STATS all_pkg_entity_ids = VALUES(all_entity_id), all_pkg_cmdline = VALUES(process.parent.command_line) WHERE all_entity_id != "null" by host.id
130
131// Find which package install entity_ids appear in this process's ancestry
132| EVAL Esql.pkg_ancestor_ids = MV_INTERSECTION(all_pkg_entity_ids, process.Ext.ancestry)
133
134// susp powershell (encoded or download cradle) and descended from an npm package install process
135| WHERE Esql.pkg_ancestor_ids IS NOT NULL AND is_susp_powershell
136
137| KEEP _id, _version, _index, data_stream.namespace, host.id, host.name, user.name, process.entity_id, process.executable, process.command_line, process.parent.executable, all_pkg_cmdline,
138 is_encoded_powershell, is_download_cradle
139'''
140
141setup = """## Setup
142
143This rule is designed for data generated by [Elastic Defend](https://www.elastic.co/security/endpoint-security), which provides native endpoint detection and response, along with event enrichments designed to work with our detection rules.
144
145Setup instructions: https://ela.st/install-elastic-defend
146"""
147
148[rule.investigation_fields]
149field_names = [
150 "host.id",
151 "host.name",
152 "user.name",
153 "process.executable",
154 "process.command_line",
155 "process.parent.executable",
156]
157
158[[rule.threat]]
159framework = "MITRE ATT&CK"
160[[rule.threat.technique]]
161id = "T1059"
162name = "Command and Scripting Interpreter"
163reference = "https://attack.mitre.org/techniques/T1059/"
164[[rule.threat.technique.subtechnique]]
165id = "T1059.001"
166name = "PowerShell"
167reference = "https://attack.mitre.org/techniques/T1059/001/"
168
169[rule.threat.tactic]
170id = "TA0002"
171name = "Execution"
172reference = "https://attack.mitre.org/tactics/TA0002/"
173
174[[rule.threat]]
175framework = "MITRE ATT&CK"
176[[rule.threat.technique]]
177id = "T1195"
178name = "Supply Chain Compromise"
179reference = "https://attack.mitre.org/techniques/T1195/"
180[[rule.threat.technique.subtechnique]]
181id = "T1195.001"
182name = "Compromise Software Dependencies and Development Tools"
183reference = "https://attack.mitre.org/techniques/T1195/001/"
184
185
186[rule.threat.tactic]
187id = "TA0001"
188name = "Initial Access"
189reference = "https://attack.mitre.org/tactics/TA0001/"
190
191[[rule.threat]]
192framework = "MITRE ATT&CK"
193[[rule.threat.technique]]
194id = "T1027"
195name = "Obfuscated Files or Information"
196reference = "https://attack.mitre.org/techniques/T1027/"
197[[rule.threat.technique.subtechnique]]
198id = "T1027.010"
199name = "Command Obfuscation"
200reference = "https://attack.mitre.org/techniques/T1027/010/"
201
202[rule.threat.tactic]
203id = "TA0005"
204name = "Defense Evasion"
205reference = "https://attack.mitre.org/tactics/TA0005/"
206
207[[rule.threat]]
208framework = "MITRE ATT&CK"
209[[rule.threat.technique]]
210id = "T1105"
211name = "Ingress Tool Transfer"
212reference = "https://attack.mitre.org/techniques/T1105/"
213
214[rule.threat.tactic]
215id = "TA0011"
216name = "Command and Control"
217reference = "https://attack.mitre.org/tactics/TA0011/"
Triage and analysis
Investigating Suspicious PowerShell from npm Package Install
PowerShell with an encoded command or a download cradle (DownloadString, Invoke-WebRequest, Invoke-Expression,
BITS) ran in a process tree that includes Node.js executing an npm package via npx-cli.js. That pattern is common
in malicious npm packages and compromised dependencies.
Possible investigation steps
- Review
all_pkg_cmdlinefor thenpx-cli.jsinvocation and identify the package name, scope, and version. - Inspect
process.command_lineand whetheris_encoded_powershelloris_download_cradleis true. Decode any-EncodedCommandpayload and extract URLs, file paths, and follow-on commands. - Correlate the same
host.idanduser.namefor new files, outbound connections, and additional child processes from the Node.js or PowerShell tree. - Check recent npm installs,
package.jsonlifecycle scripts, and lockfile changes on the host for the package inall_pkg_cmdline.
False positive analysis
- Some legitimate packages download helpers or build tools with PowerShell during install. Confirm the package, publisher, and destination URL before excluding.
- Scope exceptions to a known package command line, host, or user. Do not broadly exclude
npxorpowershell.exe.
Response and remediation
- If malicious, isolate the host, stop the Node.js and PowerShell process tree, and remove the package and any dropped files.
- Rotate credentials and tokens available to that user or build environment, including npm tokens and cloud keys.
- Block the package and related domains, and hunt for the same
all_pkg_cmdlineand PowerShell command line on other hosts.
References
Related rules
- Curl Download Activity from npm Package Install
- Execution via GitHub Actions Runner
- Potential Masquerading as Business App Installer
- RMM Software Installation from a Commonly Abused Web Service
- RMM Software Installation from an Internet-Originated MSI