@gendigital/sage-opencode 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/mcp-server.cjs +411 -88
  2. package/dist/mcp-server.cjs.map +4 -4
  3. package/dist/tool-handler.d.ts.map +1 -1
  4. package/dist/tool-handler.js +5 -3
  5. package/dist/tool-handler.js.map +1 -1
  6. package/package.json +3 -3
  7. package/resources/threats/_macros.yaml +193 -0
  8. package/resources/threats/agent-layer.yaml +19 -1
  9. package/resources/threats/commands.yaml +165 -31
  10. package/resources/threats/credentials.yaml +24 -4
  11. package/resources/threats/dummy.yaml +30 -0
  12. package/resources/threats/files.yaml +38 -0
  13. package/resources/threats/mac-commands.yaml +118 -27
  14. package/resources/threats/mac-credentials.yaml +34 -8
  15. package/resources/threats/mac-files.yaml +20 -0
  16. package/resources/threats/mac-obfuscation.yaml +28 -6
  17. package/resources/threats/mac-persistence.yaml +30 -4
  18. package/resources/threats/mac-supply-chain.yaml +12 -3
  19. package/resources/threats/mitre.yaml +325 -62
  20. package/resources/threats/obfuscation.yaml +33 -8
  21. package/resources/threats/persistence.yaml +40 -5
  22. package/resources/threats/prompt-injection.yaml +58 -0
  23. package/resources/threats/self-defense.yaml +15 -2
  24. package/resources/threats/supply_chain.yaml +6 -1
  25. package/resources/threats/urls.yaml +10 -0
  26. package/resources/threats/win-commands.yaml +294 -60
  27. package/resources/threats/win-credentials.yaml +68 -9
  28. package/resources/threats/win-files.yaml +20 -0
  29. package/resources/threats/win-obfuscation.yaml +56 -8
  30. package/resources/threats/win-persistence.yaml +45 -7
  31. package/resources/threats/win-supply-chain.yaml +5 -1
@@ -5,20 +5,30 @@
5
5
 
6
6
  # --- Pipe to shell (Remote Code Execution) ---
7
7
  - id: "CLT-CMD-001"
8
+ version: 1
9
+ detection_name: "CMD:SageCommand-A [Heur]"
8
10
  category: tool
9
11
  severity: critical
10
12
  confidence: 0.95
11
- pattern: "curl\\s[^|]*\\|\\s*(bash|sh|zsh|ksh|dash)"
13
+ # {{CMD_POS}} + {{NOT_FILENAME}} on both sides of the pipe, same shape as
14
+ # CLT-CMD-009. Had no boundary at all. curl admits an optional .exe suffix
15
+ # -- it ships as a real Windows binary (built into Windows 10+).
16
+ pattern: "{{CMD_POS}}curl(?:\\.exe)?{{NOT_FILENAME}}\\s[^|]*\\|\\s*(?:{{SHELLS}}){{NOT_FILENAME}}"
12
17
  match_on: command
13
18
  title: "Remote code execution via curl pipe to shell"
14
19
  expires_at: null
15
20
  revoked: false
16
21
 
17
22
  - id: "CLT-CMD-002"
23
+ version: 1
24
+ detection_name: "CMD:SageCommand-B [Heur]"
18
25
  category: tool
19
26
  severity: critical
20
27
  confidence: 0.95
21
- pattern: "wget\\s[^|]*\\|\\s*(bash|sh|zsh|ksh|dash)"
28
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-001. wget admits an
29
+ # optional .exe suffix -- real Windows builds exist (GnuWin32, Git for
30
+ # Windows).
31
+ pattern: "{{CMD_POS}}wget(?:\\.exe)?{{NOT_FILENAME}}\\s[^|]*\\|\\s*(?:{{SHELLS}}){{NOT_FILENAME}}"
22
32
  match_on: command
23
33
  title: "Remote code execution via wget pipe to shell"
24
34
  expires_at: null
@@ -26,30 +36,41 @@
26
36
 
27
37
  # --- Reverse shells ---
28
38
  - id: "CLT-CMD-003"
39
+ version: 1
40
+ detection_name: "CMD:SageCommand-C [Heur]"
29
41
  category: tool
30
42
  severity: critical
31
43
  confidence: 0.90
32
- pattern: "\\b(bash|sh|zsh|ksh|dash)\\s+-i\\b[\\s\\S]{0,160}/dev/tcp/"
44
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009. Bare `\b` let
45
+ # the shell name match in quoted prose.
46
+ pattern: "{{CMD_POS}}(?:{{SHELLS}}){{NOT_FILENAME}}\\s+-i\\b[\\s\\S]{0,160}/dev/tcp/"
33
47
  match_on: command
34
48
  title: "Reverse shell via /dev/tcp"
35
49
  expires_at: null
36
50
  revoked: false
37
51
 
38
52
  - id: "CLT-CMD-004"
53
+ version: 1
54
+ detection_name: "CMD:SageCommand-D [Heur]"
39
55
  category: tool
40
56
  severity: critical
41
57
  confidence: 0.90
42
- pattern: "\\bnc\\b.*\\s-e\\s"
58
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
59
+ pattern: "{{CMD_POS}}nc{{NOT_FILENAME}}.*\\s-e\\s"
43
60
  match_on: command
44
61
  title: "Reverse shell via netcat -e"
45
62
  expires_at: null
46
63
  revoked: false
47
64
 
48
65
  - id: "CLT-CMD-005"
66
+ version: 1
67
+ detection_name: "CMD:SageCommand-E [Heur]"
49
68
  category: tool
50
69
  severity: critical
51
70
  confidence: 0.85
52
- pattern: "bash\\s+-i\\s+>\\s*&\\s*/dev/"
71
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009. Had no
72
+ # boundary at all.
73
+ pattern: "{{CMD_POS}}bash{{NOT_FILENAME}}\\s+-i\\s+>\\s*&\\s*/dev/"
53
74
  match_on: command
54
75
  title: "Interactive reverse shell via bash"
55
76
  expires_at: null
@@ -57,40 +78,64 @@
57
78
 
58
79
  # --- Destructive operations ---
59
80
  - id: "CLT-CMD-006"
81
+ version: 1
82
+ detection_name: "CMD:SageCommand-F [Heur]"
60
83
  category: tool
61
84
  severity: critical
62
85
  confidence: 0.95
63
- pattern: "\\brm\\s+(-[a-zA-Z]*f[a-zA-Z]*\\s+)?(-[a-zA-Z]*r[a-zA-Z]*\\s+)?/(\\s|$|[;&|*])|\\brm\\s+(-[a-zA-Z]*r[a-zA-Z]*\\s+)?(-[a-zA-Z]*f[a-zA-Z]*\\s+)?/(\\s|$|[;&|*])|\\brm\\s+-[a-zA-Z]*rf[a-zA-Z]*\\s+/(\\s|$|[;&|*])"
86
+ # {{CMD_POS}} + {{NOT_FILENAME}} on each alternative's `rm`, same shape as
87
+ # CLT-CMD-009.
88
+ pattern: "{{CMD_POS}}rm{{NOT_FILENAME}}\\s+(-[a-zA-Z]*f[a-zA-Z]*\\s+)?(-[a-zA-Z]*r[a-zA-Z]*\\s+)?/(\\s|$|[;&|*])|{{CMD_POS}}rm{{NOT_FILENAME}}\\s+(-[a-zA-Z]*r[a-zA-Z]*\\s+)?(-[a-zA-Z]*f[a-zA-Z]*\\s+)?/(\\s|$|[;&|*])|{{CMD_POS}}rm{{NOT_FILENAME}}\\s+-[a-zA-Z]*rf[a-zA-Z]*\\s+/(\\s|$|[;&|*])"
64
89
  match_on: command
65
90
  title: "Recursive forced deletion from root"
66
91
  expires_at: null
67
92
  revoked: false
68
93
 
69
94
  - id: "CLT-CMD-007"
95
+ version: 1
96
+ detection_name: "CMD:SageCommand-G [Heur]"
70
97
  category: tool
71
98
  severity: critical
72
99
  confidence: 0.95
73
- pattern: "\\bmkfs\\b"
100
+ # {{CMD_POS}} + {{HAS_ARG}}, same shape as CLT-CMD-009. The fstype suffix
101
+ # is allowlisted ahead of {{NOT_FILENAME}}'s trailing-dot guard, since
102
+ # mkfs.<fstype> is the real invocation, not a filename, and the old
103
+ # `\bmkfs\b` matched it too. Suffixes are the actual mkfs.* binary names
104
+ # shipped by e2fsprogs, xfsprogs, btrfs-progs, dosfstools, exfatprogs,
105
+ # ntfs-3g, hfsprogs, udftools, util-linux, f2fs-tools, jfsutils,
106
+ # reiserfsprogs, nilfs-utils — not guessed shapes; a fabricated suffix like
107
+ # `fat32` is not a real binary and must not be added on spec alone.
108
+ pattern: "{{CMD_POS}}mkfs(?:\\.(?:ext[234]|xfs|btrfs|vfat|fat|msdos|exfat|ntfs|hfsplus|udf|minix|bfs|cramfs|f2fs|jfs|reiserfs|nilfs2))?{{NOT_FILENAME}}{{HAS_ARG}}"
74
109
  match_on: command
75
110
  title: "Filesystem format command"
76
111
  expires_at: null
77
112
  revoked: false
78
113
 
79
114
  - id: "CLT-CMD-008"
115
+ version: 1
116
+ detection_name: "CMD:SageCommand-H [Heur]"
80
117
  category: tool
81
118
  severity: warning
82
119
  confidence: 0.85
83
- pattern: "\\bdd\\s+if=.*of=/dev/"
120
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
121
+ pattern: "{{CMD_POS}}dd{{NOT_FILENAME}}\\s+if=.*of=/dev/"
84
122
  match_on: command
85
123
  title: "Direct disk write via dd"
86
124
  expires_at: null
87
125
  revoked: false
88
126
 
89
127
  - id: "CLT-CMD-009"
128
+ version: 1
129
+ detection_name: "CMD:SageCommand-I [Heur]"
90
130
  category: tool
91
131
  severity: critical
92
132
  confidence: 0.90
93
- pattern: "\\bshred\\b"
133
+ # Requires the binary to actually be executed: a bare word boundary matched
134
+ # filenames (`shred.py`) and path segments (`shred-latency-probe`), and a
135
+ # whitespace boundary also accepted argument position (`man shred | head`).
136
+ # `{{CMD_POS}}` is the shared vocabulary for this — see threats/_macros.yaml
137
+ # and "Writing command-position patterns" in docs/developer-guide.md.
138
+ pattern: "{{CMD_POS}}shred{{NOT_FILENAME}}{{HAS_ARG}}"
94
139
  match_on: command
95
140
  title: "Secure file destruction via shred"
96
141
  expires_at: null
@@ -98,10 +143,16 @@
98
143
 
99
144
  # --- Download + execute chains ---
100
145
  - id: "CLT-CMD-010"
146
+ version: 1
147
+ detection_name: "CMD:SageCommand-J [Heur]"
101
148
  category: tool
102
149
  severity: critical
103
150
  confidence: 0.90
104
- pattern: "(curl|wget)\\s[\\s\\S]*?(&&|;)\\s*chmod\\s+\\+x"
151
+ # {{CMD_POS}} + {{NOT_FILENAME}} on the fetcher, same shape as CLT-CMD-009,
152
+ # with an optional .exe suffix on each (real Windows binaries). `chmod`
153
+ # itself needs no boundary fix -- it's already anchored to a real
154
+ # separator (`&&`/`;`) literally in the pattern, not a bare mention.
155
+ pattern: "{{CMD_POS}}(curl|wget)(?:\\.exe)?{{NOT_FILENAME}}\\s[\\s\\S]*?(&&|;)\\s*chmod\\s+\\+x"
105
156
  match_on: command
106
157
  title: "Download and execute chain"
107
158
  expires_at: null
@@ -109,16 +160,21 @@
109
160
 
110
161
  # --- Privilege escalation ---
111
162
  - id: "CLT-CMD-011"
163
+ version: 1
164
+ detection_name: "CMD:SageCommand-K [Heur]"
112
165
  category: tool
113
166
  severity: warning
114
167
  confidence: 0.60
115
- pattern: "\\bchmod\\s+777\\b"
168
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
169
+ pattern: "{{CMD_POS}}chmod{{NOT_FILENAME}}\\s+777\\b"
116
170
  match_on: command
117
171
  title: "Overly permissive file permissions"
118
172
  expires_at: null
119
173
  revoked: false
120
174
 
121
175
  - id: "CLT-CMD-012"
176
+ version: 1
177
+ detection_name: "CMD:SageCommand-L [Heur]"
122
178
  category: tool
123
179
  severity: warning
124
180
  confidence: 0.60
@@ -130,10 +186,14 @@
130
186
 
131
187
  # --- Data exfiltration ---
132
188
  - id: "CLT-CMD-013"
189
+ version: 1
190
+ detection_name: "CMD:SageCommand-M [Heur]"
133
191
  category: network_egress
134
192
  severity: warning
135
193
  confidence: 0.60
136
- pattern: "curl\\s[^|;]*(-d|--data|--data-binary)\\s\\S*(/etc/passwd|/etc/shadow|\\.ssh/|id_rsa|credentials|secrets)"
194
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009. Had no
195
+ # boundary at all. curl admits an optional .exe suffix, same as CLT-CMD-001.
196
+ pattern: "{{CMD_POS}}curl(?:\\.exe)?{{NOT_FILENAME}}\\s[^|;]*(-d|--data|--data-binary)\\s\\S*(/etc/passwd|/etc/shadow|\\.ssh/|id_rsa|credentials|secrets)"
137
197
  match_on: command
138
198
  title: "Possible data exfiltration of sensitive files"
139
199
  expires_at: null
@@ -141,10 +201,18 @@
141
201
 
142
202
  # --- Privileged pipe to shell ---
143
203
  - id: "CLT-CMD-014"
204
+ version: 1
205
+ detection_name: "CMD:SageCommand-N [Heur]"
144
206
  category: tool
145
207
  severity: critical
146
208
  confidence: 0.95
147
- pattern: "\\bsudo\\s+(curl|wget)\\s[^|]*\\|\\s*(bash|sh|zsh|ksh|dash)"
209
+ # {{CMD_POS}} + {{NOT_FILENAME}} on `sudo` itself, not on curl/wget --
210
+ # this rule means "privileged", so it must keep requiring the literal
211
+ # `sudo` wrapper rather than folding into {{CMD_POS}}'s generic wrapper
212
+ # allowance (which would also admit the unprivileged case CLT-CMD-001
213
+ # already covers). curl/wget admit an optional .exe suffix, same as
214
+ # CLT-CMD-001/002.
215
+ pattern: "{{CMD_POS}}sudo{{NOT_FILENAME}}\\s+(curl|wget)(?:\\.exe)?{{NOT_FILENAME}}\\s[^|]*\\|\\s*(?:{{SHELLS}}){{NOT_FILENAME}}"
148
216
  match_on: command
149
217
  title: "Privileged pipe-to-shell (sudo curl/wget piped to shell)"
150
218
  expires_at: null
@@ -156,10 +224,14 @@
156
224
  # Uses `[\s\S]` to span newlines (loader does not set the `s` flag) and a
157
225
  # bounded non-greedy quantifier to avoid catastrophic backtracking.
158
226
  - id: "CLT-CMD-015"
227
+ version: 1
228
+ detection_name: "CMD:SageCommand-O [Heur]"
159
229
  category: tool
160
230
  severity: warning
161
231
  confidence: 0.60
162
- pattern: "python[3]?\\s+-c\\s+[\"'][\\s\\S]{0,2048}?(?:os\\.(?:system|popen|exec[lv]?[ep]?e?)|subprocess\\.(?:run|call|Popen|check_output|check_call|getoutput|getstatusoutput)|__import__)\\s*\\("
232
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009. Had no
233
+ # boundary at all.
234
+ pattern: "{{CMD_POS}}python[3]?{{NOT_FILENAME}}\\s+-c\\s+[\"'][\\s\\S]{0,2048}?(?:os\\.(?:system|popen|exec[lv]?[ep]?e?)|subprocess\\.(?:run|call|Popen|check_output|check_call|getoutput|getstatusoutput)|__import__)\\s*\\("
163
235
  match_on: command
164
236
  title: "Python one-liner invoking shell/exec/subprocess call"
165
237
  expires_at: null
@@ -167,43 +239,68 @@
167
239
 
168
240
  # --- Indirect execution patterns ---
169
241
  - id: "CLT-CMD-016"
242
+ version: 1
243
+ detection_name: "CMD:SageCommand-P [Heur]"
170
244
  category: tool
171
245
  severity: critical
172
246
  confidence: 0.90
173
- pattern: "\\bbash\\s+<\\(\\s*(curl|wget)\\b"
247
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
248
+ pattern: "{{CMD_POS}}bash{{NOT_FILENAME}}\\s+<\\(\\s*(curl|wget)\\b"
174
249
  match_on: command
175
250
  title: "Process substitution executing remote content"
176
251
  expires_at: null
177
252
  revoked: false
178
253
 
179
254
  - id: "CLT-CMD-017"
255
+ version: 1
256
+ detection_name: "CMD:SageCommand-Q [Heur]"
180
257
  category: tool
181
258
  severity: warning
182
259
  confidence: 0.85
183
- pattern: "\\beval\\b[^;|]*\\b(curl|wget)\\b"
260
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
261
+ pattern: "{{CMD_POS}}eval{{NOT_FILENAME}}[^;|]*\\b(curl|wget)\\b"
184
262
  match_on: command
185
263
  title: "Eval executing remote download command"
186
264
  expires_at: null
187
265
  revoked: false
188
266
 
189
267
  - id: "CLT-CMD-018"
268
+ version: 1
269
+ detection_name: "CMD:SageCommand-R [Heur]"
190
270
  category: tool
191
271
  severity: warning
192
272
  confidence: 0.85
193
- # The lookarounds prevent matching short-option flags like `du -sh` or
194
- # identifiers like `shell`/`bashrc`; only standalone command invocations
195
- # (preceded by whitespace, `/`, etc.) are flagged.
196
- pattern: "\\bxargs\\b[^|;\\r\\n]{0,400}(?:(?<![-\\w])(bash|sh)(?![-\\w])(?:\\s+-[\\w]*c\\b\\s+(?:\"(?!\\s*(?:head|cat|wc)\\b)[^\"\\r\\n]{0,240}(?:\\{\\}|\\$[@*]|\\$\\d)[^\"\\r\\n]{0,240}\"|'(?!\\s*(?:head|cat|wc)\\b)[^'\\r\\n]{0,240}(?:\\{\\}|\\$[@*]|\\$\\d)[^'\\r\\n]{0,240}')|(?!\\s+-[\\w]*c\\b))|(?<![-\\w])(curl|wget)(?![-\\w])[^|;\\r\\n]{0,400}(?:-d|--data|--data-binary|--upload-file|-T|--post-file|--post-data|--body-file)\\b)"
273
+ # `xargs` is itself anchored with {{CMD_POS}} rather than a bare `\bxargs\b`
274
+ # (which fired on prose merely mentioning xargs). `{{NOT_COMPOUND_LEFT}}`
275
+ # around the dispatched shell/curl name was also a misapplication of that
276
+ # macro: it exists for identifiers that are argument *values* (a WMI class
277
+ # name), never program names, whereas the shell/curl here really is being
278
+ # run. `xargs` is already one of {{CMD_PREFIX}}'s wrapper tokens, so a
279
+ # second `{{CMD_PREFIX}}` application right after the anchored `xargs`
280
+ # naturally skips its own flags (`-I{}`, `-n1`, `-P4`, an intervening
281
+ # `sudo`, ...) before the dispatched program, the same way {{CMD_POS}}
282
+ # skips wrapper tokens before any other program name. Anchoring `xargs`
283
+ # itself (rather than folding it into a generic {{CMD_POS}} on the
284
+ # shell/curl name) keeps this rule scoped to xargs's `{}`-substitution
285
+ # danger specifically — `sudo bash -c "echo {}"` with no xargs anywhere is
286
+ # inert literal text, not a real invocation, and must not fire here (that's
287
+ # CLT-CMD-009/017's territory, not this rule's). curl/wget admit an
288
+ # optional .exe suffix, same as CLT-CMD-001/002; {{SHELLS}} does too for
289
+ # powershell/pwsh via the shared macro.
290
+ pattern: "{{CMD_POS}}xargs{{NOT_FILENAME}}\\s+{{CMD_PREFIX}}(?:(?:{{SHELLS}}){{NOT_FILENAME}}(?:\\s+-[\\w]*c\\b\\s+(?:\"(?!\\s*(?:head|cat|wc)\\b)[^\"\\r\\n]{0,240}(?:\\{\\}|\\$[@*]|\\$\\d)[^\"\\r\\n]{0,240}\"|'(?!\\s*(?:head|cat|wc)\\b)[^'\\r\\n]{0,240}(?:\\{\\}|\\$[@*]|\\$\\d)[^'\\r\\n]{0,240}')|(?!\\s+-[\\w]*c\\b))|(curl|wget)(?:\\.exe)?{{NOT_FILENAME}}[^|;\\r\\n]{0,400}(?:-d|--data|--data-binary|--upload-file|-T|--post-file|--post-data|--body-file)\\b)"
197
291
  match_on: command
198
292
  title: "xargs dispatching download or shell execution"
199
293
  expires_at: null
200
294
  revoked: false
201
295
 
202
296
  - id: "CLT-CMD-019"
297
+ version: 1
298
+ detection_name: "CMD:SageCommand-S [Heur]"
203
299
  category: tool
204
300
  severity: warning
205
301
  confidence: 0.85
206
- pattern: "\\bfind\\b[^|;]*-exec\\s+(bash|sh|curl|wget)\\b"
302
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
303
+ pattern: "{{CMD_POS}}find{{NOT_FILENAME}}[^|;]*-exec\\s+(?:{{SHELLS}}|curl|wget)\\b"
207
304
  match_on: command
208
305
  title: "find -exec executing shell or download commands"
209
306
  expires_at: null
@@ -211,20 +308,26 @@
211
308
 
212
309
  # --- DNS exfiltration ---
213
310
  - id: "CLT-CMD-020"
311
+ version: 1
312
+ detection_name: "CMD:SageCommand-T [Heur]"
214
313
  category: network_egress
215
314
  severity: warning
216
315
  confidence: 0.80
217
- pattern: "\\b(nslookup|dig|host)\\b[^|;]*\\$\\([^)]+\\)\\.[a-zA-Z]"
316
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
317
+ pattern: "{{CMD_POS}}(nslookup|dig|host){{NOT_FILENAME}}[^|;]*\\$\\([^)]+\\)\\.[a-zA-Z]"
218
318
  match_on: command
219
319
  title: "DNS exfiltration via command substitution as subdomain label"
220
320
  expires_at: null
221
321
  revoked: false
222
322
 
223
323
  - id: "CLT-CMD-021"
324
+ version: 1
325
+ detection_name: "CMD:SageCommand-U [Heur]"
224
326
  category: network_egress
225
327
  severity: warning
226
328
  confidence: 0.80
227
- pattern: "\\b(nslookup|dig|host)\\b[^|;]*`[^`]+`\\.[a-zA-Z]"
329
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-020.
330
+ pattern: "{{CMD_POS}}(nslookup|dig|host){{NOT_FILENAME}}[^|;]*`[^`]+`\\.[a-zA-Z]"
228
331
  match_on: command
229
332
  title: "DNS exfiltration via backtick substitution as subdomain label"
230
333
  expires_at: null
@@ -232,10 +335,18 @@
232
335
 
233
336
  # --- Loop-based indirect execution ---
234
337
  - id: "CLT-CMD-022"
338
+ version: 1
339
+ detection_name: "CMD:SageCommand-V [Heur]"
235
340
  category: tool
236
341
  severity: warning
237
342
  confidence: 0.80
238
- pattern: "\\b(while|for)\\b[^;]*;\\s*do[^;]*(\\b(bash|sh)\\s+-c\\b|\\b(curl|wget)\\b[^;]*\\|\\s*(?:sudo\\s+)?(?:env\\s+)?(?:[^\\s\"']*[\\\\/])?(bash|sh|zsh|ksh|dash)\\b|\\b(curl|wget)\\b[^;]*(?:(-d|--data|--data-binary)\\s+['\\\"]?@\\S+|(--upload-file|-T)\\s+['\\\"]?\\S+))"
343
+ # {{CMD_POS}} anchors the loop keyword itself so prose merely describing
344
+ # the shape (`echo "for url in $URLS; do curl $url | sh; done ..."`) no
345
+ # longer fires. The bare `\b` on the inner shell/curl/wget mentions is left
346
+ # as-is: they're only reachable once a real `for`/`while ...; do` has
347
+ # already matched at command position, so they aren't an independent
348
+ # prose-mention surface the way the outer keyword was.
349
+ pattern: "{{CMD_POS}}(while|for){{NOT_FILENAME}}[^;]*;\\s*do[^;]*(\\b(?:{{SHELLS}})\\s+-c\\b|\\b(curl|wget)\\b[^;]*\\|\\s*(?:sudo\\s+)?(?:env\\s+)?(?:[^\\s\"']*[\\\\/])?(?:{{SHELLS}})\\b|\\b(curl|wget)\\b[^;]*(?:(-d|--data|--data-binary)\\s+['\\\"]?@\\S+|(--upload-file|-T)\\s+['\\\"]?\\S+))"
239
350
  match_on: command
240
351
  title: "Loop executing shell or download commands"
241
352
  expires_at: null
@@ -245,10 +356,14 @@
245
356
 
246
357
  # Python socket reverse shell
247
358
  - id: "CLT-CMD-023"
359
+ version: 1
360
+ detection_name: "CMD:SageCommand-W [Heur]"
248
361
  category: tool
249
362
  severity: critical
250
363
  confidence: 0.90
251
- pattern: "python[23]?\\s+-c.*socket.*connect.*(\\/bin\\/zsh|\\/bin\\/sh|\\/bin\\/bash)"
364
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-015. Had no
365
+ # boundary at all.
366
+ pattern: "{{CMD_POS}}python[23]?{{NOT_FILENAME}}\\s+-c.*socket.*connect.*(\\/bin\\/zsh|\\/bin\\/sh|\\/bin\\/bash)"
252
367
  match_on: command
253
368
  title: "Python reverse shell via socket"
254
369
  expires_at: null
@@ -256,10 +371,14 @@
256
371
 
257
372
  # Ruby TCPSocket reverse shell
258
373
  - id: "CLT-CMD-024"
374
+ version: 1
375
+ detection_name: "CMD:SageCommand-X [Heur]"
259
376
  category: tool
260
377
  severity: critical
261
378
  confidence: 0.90
262
- pattern: "ruby\\s+-e.*TCPSocket.*exec.*(\\/bin\\/zsh|\\/bin\\/sh)"
379
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009. Had no
380
+ # boundary at all.
381
+ pattern: "{{CMD_POS}}ruby{{NOT_FILENAME}}\\s+-e.*TCPSocket.*exec.*(\\/bin\\/zsh|\\/bin\\/sh)"
263
382
  match_on: command
264
383
  title: "Ruby reverse shell via TCPSocket"
265
384
  expires_at: null
@@ -267,10 +386,13 @@
267
386
 
268
387
  # zsh -c reverse shell pattern
269
388
  - id: "CLT-CMD-025"
389
+ version: 1
390
+ detection_name: "CMD:SageCommand-Y [Heur]"
270
391
  category: tool
271
392
  severity: critical
272
393
  confidence: 0.90
273
- pattern: "\\bzsh\\s+-c\\b.*\\b(exec|socket|/dev/tcp)\\b"
394
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
395
+ pattern: "{{CMD_POS}}zsh{{NOT_FILENAME}}\\s+-c\\b.*\\b(exec|socket|/dev/tcp)\\b"
274
396
  match_on: command
275
397
  title: "Zsh reverse shell pattern"
276
398
  expires_at: null
@@ -278,10 +400,13 @@
278
400
 
279
401
  # --- Recursive deletion of critical system directories ---
280
402
  - id: "CLT-CMD-026"
403
+ version: 1
404
+ detection_name: "CMD:SageCommand-Z [Heur]"
281
405
  category: tool
282
406
  severity: critical
283
407
  confidence: 0.95
284
- pattern: "\\brm\\s+(-[a-zA-Z]+\\s+)*-[a-zA-Z]*[rR][a-zA-Z]*(\\s+-[a-zA-Z]+)*\\s+/(home|Users|etc|usr|var|boot|bin|sbin|lib|lib64|opt|root|System|Applications|Library|Volumes|private|mnt)\\b/?(\\s|$|[;&|])"
408
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-006.
409
+ pattern: "{{CMD_POS}}rm{{NOT_FILENAME}}\\s+(-[a-zA-Z]+\\s+)*-[a-zA-Z]*[rR][a-zA-Z]*(\\s+-[a-zA-Z]+)*\\s+/(home|Users|etc|usr|var|boot|bin|sbin|lib|lib64|opt|root|System|Applications|Library|Volumes|private|mnt)\\b/?(\\s|$|[;&|])"
285
410
  match_on: command
286
411
  title: "Recursive deletion of critical system directory"
287
412
  expires_at: null
@@ -289,10 +414,13 @@
289
414
 
290
415
  # --- Deletion of .env files ---
291
416
  - id: "CLT-CMD-027"
417
+ version: 1
418
+ detection_name: "CMD:SageCommand-AA [Heur]"
292
419
  category: tool
293
420
  severity: warning
294
421
  confidence: 0.60
295
- pattern: "\\brm\\s+(-{1,2}[\\w-]+(=\\S+)?\\s+)*(\\S+[\\\\/])?\\.env(?!\\S*\\.(example|sample|template|dist)\\b)(\\.(local|production|prod|staging|stage|development|dev|test))?\\b"
422
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-006.
423
+ pattern: "{{CMD_POS}}rm{{NOT_FILENAME}}\\s+(-{1,2}[\\w-]+(=\\S+)?\\s+)*(\\S+[\\\\/])?\\.env(?!\\S*\\.(example|sample|template|dist)\\b)(\\.(local|production|prod|staging|stage|development|dev|test))?\\b"
296
424
  match_on: command
297
425
  title: "Deletion of .env file"
298
426
  expires_at: null
@@ -300,10 +428,13 @@
300
428
 
301
429
  # --- Deletion of database files ---
302
430
  - id: "CLT-CMD-028"
431
+ version: 1
432
+ detection_name: "CMD:SageCommand-AB [Heur]"
303
433
  category: tool
304
434
  severity: warning
305
435
  confidence: 0.60
306
- pattern: "\\brm\\s+(-{1,2}[\\w-]+(=\\S+)?\\s+)*\\S+\\.(db|sqlite|sqlite3)\\b"
436
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-006.
437
+ pattern: "{{CMD_POS}}rm{{NOT_FILENAME}}\\s+(-{1,2}[\\w-]+(=\\S+)?\\s+)*\\S+\\.(db|sqlite|sqlite3)\\b"
307
438
  match_on: command
308
439
  title: "Deletion of database file"
309
440
  expires_at: null
@@ -311,10 +442,13 @@
311
442
 
312
443
  # --- Deletion of .git directory ---
313
444
  - id: "CLT-CMD-029"
445
+ version: 1
446
+ detection_name: "CMD:SageCommand-AC [Heur]"
314
447
  category: tool
315
448
  severity: warning
316
449
  confidence: 0.60
317
- pattern: "\\b(rm|del|rmdir|rd|Remove-Item|unlink)\\b.*(\\s|[\\\\/])\\.git(\\s|[\\\\/]|$)"
450
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-SELF-001.
451
+ pattern: "{{CMD_POS}}(rm|del|rmdir|rd|Remove-Item|unlink){{NOT_FILENAME}}.*(\\s|[\\\\/])\\.git(\\s|[\\\\/]|$)"
318
452
  case_insensitive: true
319
453
  match_on: command
320
454
  title: "Deletion of .git directory"
@@ -4,6 +4,8 @@
4
4
 
5
5
  # --- Known API key prefixes ---
6
6
  - id: "CLT-CRED-001"
7
+ version: 1
8
+ detection_name: "CMD:SageCredential-A [Heur]"
7
9
  category: secrets
8
10
  severity: critical
9
11
  confidence: 0.80
@@ -15,10 +17,13 @@
15
17
 
16
18
  # --- Export of credential env vars ---
17
19
  - id: "CLT-CRED-002"
20
+ version: 1
21
+ detection_name: "CMD:SageCredential-B [Heur]"
18
22
  category: secrets
19
23
  severity: warning
20
24
  confidence: 0.60
21
- pattern: "\\bexport\\s+[A-Z_]*(API_KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|AUTH)[A-Z_]*="
25
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
26
+ pattern: "{{CMD_POS}}export{{NOT_FILENAME}}\\s+[A-Z_]*(API_KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|AUTH)[A-Z_]*="
22
27
  match_on: command
23
28
  title: "Export of environment variable with credential keyword"
24
29
  expires_at: null
@@ -26,6 +31,8 @@
26
31
 
27
32
  # --- .env file writes ---
28
33
  - id: "CLT-CRED-003"
34
+ version: 1
35
+ detection_name: "CMD:SageCredential-C [Heur]"
29
36
  category: secrets
30
37
  severity: warning
31
38
  confidence: 0.60
@@ -37,10 +44,13 @@
37
44
 
38
45
  # --- Reading credential files ---
39
46
  - id: "CLT-CRED-004"
47
+ version: 1
48
+ detection_name: "CMD:SageCredential-D [Heur]"
40
49
  category: secrets
41
50
  severity: warning
42
51
  confidence: 0.60
43
- pattern: "\\b(cat|less|more|head|tail)\\s+.*(\\.(env(?!\\S*\\.(example|sample|template|dist)\\b)(\\.(local|production|prod|staging|stage|development|dev|test))?)|credentials|\\.aws/credentials|\\.ssh/id_rsa|\\.ssh/id_ed25519|\\.netrc|\\.pgpass)"
52
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
53
+ pattern: "{{CMD_POS}}(cat|less|more|head|tail){{NOT_FILENAME}}\\s+.*(\\.(env(?!\\S*\\.(example|sample|template|dist)\\b)(\\.(local|production|prod|staging|stage|development|dev|test))?)|credentials|\\.aws/credentials|\\.ssh/id_rsa|\\.ssh/id_ed25519|\\.netrc|\\.pgpass)"
44
54
  match_on: command
45
55
  title: "Reading credential or secret file"
46
56
  expires_at: null
@@ -48,6 +58,8 @@
48
58
 
49
59
  # --- Plaintext credential assignment ---
50
60
  - id: "CLT-CRED-005"
61
+ version: 1
62
+ detection_name: "CMD:SageCredential-E [Heur]"
51
63
  category: secrets
52
64
  severity: warning
53
65
  confidence: 0.60
@@ -59,6 +71,8 @@
59
71
 
60
72
  # --- Long-form API key patterns ---
61
73
  - id: "CLT-CRED-006"
74
+ version: 1
75
+ detection_name: "CMD:SageCredential-F [Heur]"
62
76
  category: secrets
63
77
  severity: critical
64
78
  confidence: 0.92
@@ -70,13 +84,16 @@
70
84
 
71
85
  # --- Copy/move/link secret files ---
72
86
  - id: "CLT-CRED-007"
87
+ version: 1
88
+ detection_name: "CMD:SageCredential-G [Heur]"
73
89
  category: secrets
74
90
  severity: warning
75
91
  confidence: 0.60
76
92
  # Protected files: .env*, .aws/credentials, .ssh/(id_rsa|id_ed25519|id_ecdsa|id_dsa),
77
93
  # .netrc, .pgpass, .npmrc, .pypirc, key.properties, .docker/config.json,
78
94
  # kubeconfig, .kube/config, master.key, credentials.yml
79
- pattern: "\\b(cp|mv|ln|rsync|install|ditto)\\s+.*(?<=[\\\\/\\s])(\\.env(?!\\S*\\.(example|sample|template|dist)\\b)(\\.\\w+)?(?=[\\s\\\\/]|$)|\\.aws/credentials|\\.ssh/(id_rsa|id_ed25519|id_ecdsa|id_dsa)|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc|key\\.properties|\\.docker/config\\.json|kubeconfig|\\.kube/config|master\\.key|credentials\\.yml)"
95
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
96
+ pattern: "{{CMD_POS}}(cp|mv|ln|rsync|install|ditto){{NOT_FILENAME}}\\s+.*(?<=[\\\\/\\s])(\\.env(?!\\S*\\.(example|sample|template|dist)\\b)(\\.\\w+)?(?=[\\s\\\\/]|$)|\\.aws/credentials|\\.ssh/(id_rsa|id_ed25519|id_ecdsa|id_dsa)|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc|key\\.properties|\\.docker/config\\.json|kubeconfig|\\.kube/config|master\\.key|credentials\\.yml)"
80
97
  match_on: command
81
98
  title: "Copy/move/link of credential or secret file"
82
99
  expires_at: null
@@ -84,13 +101,16 @@
84
101
 
85
102
  # --- Archive secret files ---
86
103
  - id: "CLT-CRED-008"
104
+ version: 1
105
+ detection_name: "CMD:SageCredential-H [Heur]"
87
106
  category: secrets
88
107
  severity: warning
89
108
  confidence: 0.60
90
109
  # Protected files: .env*, .aws/credentials, .ssh/(id_rsa|id_ed25519|id_ecdsa|id_dsa),
91
110
  # .netrc, .pgpass, .npmrc, .pypirc, key.properties, .docker/config.json,
92
111
  # kubeconfig, .kube/config, master.key, credentials.yml
93
- pattern: "\\b(tar|zip|7z|rar|gzip)\\s+.*(?<=[\\\\/\\s])(\\.env(?!\\S*\\.(example|sample|template|dist)\\b)(\\.\\w+)?(?=[\\s\\\\/]|$)|\\.aws/credentials|\\.ssh/(id_rsa|id_ed25519|id_ecdsa|id_dsa)|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc|key\\.properties|\\.docker/config\\.json|kubeconfig|\\.kube/config|master\\.key|credentials\\.yml)"
112
+ # {{CMD_POS}} + {{NOT_FILENAME}}, same shape as CLT-CMD-009.
113
+ pattern: "{{CMD_POS}}(tar|zip|7z|rar|gzip){{NOT_FILENAME}}\\s+.*(?<=[\\\\/\\s])(\\.env(?!\\S*\\.(example|sample|template|dist)\\b)(\\.\\w+)?(?=[\\s\\\\/]|$)|\\.aws/credentials|\\.ssh/(id_rsa|id_ed25519|id_ecdsa|id_dsa)|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc|key\\.properties|\\.docker/config\\.json|kubeconfig|\\.kube/config|master\\.key|credentials\\.yml)"
94
114
  match_on: command
95
115
  title: "Archiving credential or secret file"
96
116
  expires_at: null