Curl Download Activity from npm Package Install
Detects curl downloading over HTTP(S) with a short argument list, writing output to a file or launched via a shell,
when the process ancestry includes a Node.js npm package execution (npx-cli.js or an npx cache path). Malicious npm
packages and supply-chain compromises commonly fetch a second stage from install or postinstall scripts.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2026/09/23"
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/23"
8
9[rule]
10author = ["Elastic"]
11description = """
12Detects curl downloading over HTTP(S) with a short argument list, writing output to a file or launched via a shell,
13when the process ancestry includes a Node.js npm package execution (`npx-cli.js` or an npx cache path). Malicious npm
14packages and supply-chain compromises commonly fetch a second stage from install or postinstall scripts.
15"""
16from = "now-9m"
17language = "esql"
18license = "Elastic License v2"
19name = "Curl Download Activity from npm Package Install"
20note = """## Triage and analysis
21
22### Investigating Curl Download Activity from npm Package Install
23
24curl retrieved a remote HTTP(S) resource with a short command line (output file `-o`, or a shell parent) in a process
25tree that includes Node.js executing an npm package via `npx-cli.js` or the npx cache. That pattern is common in
26malicious npm packages and compromised dependencies.
27
28### Possible investigation steps
29
30- Review `all_pkg_cmdline` for the `npx-cli.js` or npx-cache invocation and identify the package name, scope, and version.
31- Inspect `process.command_line` for the URL, `-o`/`-O` output path, and whether the download is piped to a shell.
32- Correlate the same `host.id` and `user.name` for newly written files, outbound connections, and additional children
33 from the Node.js or curl tree.
34- Check recent npm installs, `package.json` lifecycle scripts, and lockfile changes on the host for the package in
35 `all_pkg_cmdline`.
36
37### False positive analysis
38
39- Some legitimate packages download helpers or build tools with curl during install. Confirm the package, publisher,
40 and destination URL before excluding.
41- Scope exceptions to a known package command line, host, or user. Do not broadly exclude `npx` or `curl`.
42
43### Response and remediation
44
45- If malicious, isolate the host, stop the Node.js and curl process tree, and remove the package and any dropped files.
46- Rotate credentials and tokens available to that user or build environment, including npm tokens and cloud keys.
47- Block the package and related domains, and hunt for the same `all_pkg_cmdline` and curl command line on other hosts.
48"""
49references = ["https://www.elastic.co/blog/shai-hulud-worm-npm-supply-chain-compromise"]
50risk_score = 47
51rule_id = "7a3c9e21-5d84-4b16-9f02-1e8c6a4d0b73"
52severity = "medium"
53tags = [
54 "Domain: Endpoint",
55 "OS: Linux",
56 "OS: macOS",
57 "Platform: Linux",
58 "Platform: macOS",
59 "Use Case: Threat Detection",
60 "Tactic: Command and Control",
61 "Tactic: Execution",
62 "Tactic: Initial Access",
63 "Data Source: Elastic Defend",
64 "Resources: Investigation Guide",
65 "Rule Type: ES|QL",
66 "Threat: Supply Chain",
67 "Threat: Download Tool Abuse",
68]
69timestamp_override = "event.ingested"
70type = "esql"
71
72query = '''
73FROM logs-endpoint.events.process-* METADATA _id, _version, _index
74
75| WHERE KQL(""" host.os.type:("linux" or "macos") AND event.action:exec """)
76
77| EVAL is_pkg_install = CASE(
78 // npm
79 process.parent.name == "node" 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/_npx/*" OR
83 process.parent.command_line LIKE "*/npm-cache/*"
84 ), true,
85 false
86 )
87
88| EVAL cmdline_lower = TO_LOWER(process.command_line)
89| EVAL parent_cmdline_lower = TO_LOWER(process.parent.command_line)
90
91| EVAL is_curl_download = CASE(
92 process.name == "curl" AND
93 cmdline_lower LIKE "*http*" AND
94 process.args_count <= 5 AND
95 (
96 cmdline_lower LIKE "* -o*" OR
97 cmdline_lower LIKE "* --output*" OR
98 parent_cmdline_lower LIKE "*zsh" OR
99 parent_cmdline_lower LIKE "*sh" OR
100 parent_cmdline_lower LIKE "*bash" OR
101 parent_cmdline_lower LIKE "*dash"
102 ) AND
103 NOT (
104 cmdline_lower LIKE "*169.254.169.254*" OR
105 cmdline_lower LIKE "*localhost*" OR
106 cmdline_lower LIKE "*127.0.0.1*"
107 ),
108 true, false
109 )
110
111| WHERE process.Ext.ancestry IS NOT NULL AND (is_curl_download OR is_pkg_install)
112
113// Capture entity_ids for package install parent processes
114| EVAL all_entity_id = CASE(is_pkg_install, process.parent.entity_id, "null")
115
116// Collect all package install entity_ids globally
117| 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
118
119// Find which package install entity_ids appear in this process's ancestry
120| EVAL Esql.pkg_ancestor_ids = MV_INTERSECTION(all_pkg_entity_ids, process.Ext.ancestry)
121
122// curl download descended from an npm package install process
123| WHERE Esql.pkg_ancestor_ids IS NOT NULL AND is_curl_download
124
125| KEEP _id, _version, _index, data_stream.namespace, host.id, host.name, user.name, process.entity_id, process.executable, process.command_line, process.parent.executable, process.parent.command_line, all_pkg_cmdline
126'''
127
128setup = """## Setup
129
130This 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.
131
132Setup instructions: https://ela.st/install-elastic-defend
133"""
134
135[rule.investigation_fields]
136field_names = [
137 "host.id",
138 "host.name",
139 "user.name",
140 "process.executable",
141 "process.command_line",
142 "process.parent.executable",
143 "process.parent.command_line",
144]
145
146[[rule.threat]]
147framework = "MITRE ATT&CK"
148[[rule.threat.technique]]
149id = "T1105"
150name = "Ingress Tool Transfer"
151reference = "https://attack.mitre.org/techniques/T1105/"
152
153[rule.threat.tactic]
154id = "TA0011"
155name = "Command and Control"
156reference = "https://attack.mitre.org/tactics/TA0011/"
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.004"
166name = "Unix Shell"
167reference = "https://attack.mitre.org/techniques/T1059/004/"
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[rule.threat.tactic]
186id = "TA0001"
187name = "Initial Access"
188reference = "https://attack.mitre.org/tactics/TA0001/"
Triage and analysis
Investigating Curl Download Activity from npm Package Install
curl retrieved a remote HTTP(S) resource with a short command line (output file -o, or a shell parent) in a process
tree that includes Node.js executing an npm package via npx-cli.js or the npx cache. That pattern is common in
malicious npm packages and compromised dependencies.
Possible investigation steps
- Review
all_pkg_cmdlinefor thenpx-cli.jsor npx-cache invocation and identify the package name, scope, and version. - Inspect
process.command_linefor the URL,-o/-Ooutput path, and whether the download is piped to a shell. - Correlate the same
host.idanduser.namefor newly written files, outbound connections, and additional children from the Node.js or curl 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 curl 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
npxorcurl.
Response and remediation
- If malicious, isolate the host, stop the Node.js and curl 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 curl command line on other hosts.
References
Related rules
- Curl or Wget Spawned via Node.js
- Execution via GitHub Actions Runner
- Remote GitHub Actions Runner Registration
- Tampering with RUNNER_TRACKING_ID in GitHub Actions Runners
- Network Connection to OAST Domain via Script Interpreter