agent-security-scanner-mcp 3.8.0 → 3.10.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.
- package/README.md +232 -5
- package/index.js +81 -1
- package/openclaw.plugin.json +41 -0
- package/package.json +4 -1
- package/regex_fallback.py +3 -1
- package/rules/clawhavoc.yaml +443 -0
- package/src/cli/audit.js +18 -0
- package/src/cli/harden.js +15 -0
- package/src/context.js +4 -0
- package/src/daemon-client.js +10 -0
- package/src/plugin-config.js +77 -0
- package/src/plugin-health.js +49 -0
- package/src/tools/scan-mcp.js +344 -10
- package/src/tools/scan-security.js +32 -5
- package/src/tools/scan-skill.js +743 -0
- package/src/utils.js +58 -0
- package/src/tools/garak-bridge.js +0 -209
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# ClawHavoc Malware Signature Rules
|
|
2
|
+
# ===================================
|
|
3
|
+
# ClawHavoc is a threat intelligence database for detecting known malicious
|
|
4
|
+
# patterns in OpenClaw skills. These signatures cover reverse shells, crypto
|
|
5
|
+
# miners, info stealers, keyloggers, C2 beacons, and OpenClaw-specific attacks
|
|
6
|
+
# observed in the wild. Each rule maps to a MITRE ATT&CK technique or known
|
|
7
|
+
# malware family. Patterns are JavaScript-compatible regex (used with RegExp).
|
|
8
|
+
#
|
|
9
|
+
# Maintained by the ClawProof security team.
|
|
10
|
+
# Last updated: 2026-02-18
|
|
11
|
+
|
|
12
|
+
rules:
|
|
13
|
+
# ==========================================================================
|
|
14
|
+
# CATEGORY 1: REVERSE SHELLS
|
|
15
|
+
# ==========================================================================
|
|
16
|
+
|
|
17
|
+
- id: clawhavoc.revshell.bash
|
|
18
|
+
severity: CRITICAL
|
|
19
|
+
message: "Bash reverse shell detected — opens interactive shell over TCP"
|
|
20
|
+
patterns:
|
|
21
|
+
- "bash\\s+-i\\s+>&\\s*/dev/tcp/"
|
|
22
|
+
- "/dev/tcp/\\d+\\.\\d+\\.\\d+\\.\\d+/\\d+"
|
|
23
|
+
- "bash\\s+.*>\\s*/dev/tcp/.*<&"
|
|
24
|
+
- "0<&\\d+;\\s*exec\\s+\\d+<>/dev/tcp/"
|
|
25
|
+
metadata:
|
|
26
|
+
category: reverse_shell
|
|
27
|
+
action: BLOCK
|
|
28
|
+
confidence: HIGH
|
|
29
|
+
malware_family: generic
|
|
30
|
+
|
|
31
|
+
- id: clawhavoc.revshell.netcat
|
|
32
|
+
severity: CRITICAL
|
|
33
|
+
message: "Netcat reverse shell detected — executes shell via nc/ncat"
|
|
34
|
+
patterns:
|
|
35
|
+
- "\\bnc\\s+-e\\s+/bin/(ba)?sh"
|
|
36
|
+
- "\\bncat\\s+-e\\s+/bin/(ba)?sh"
|
|
37
|
+
- "\\bnetcat\\s+-e\\s+/bin/(ba)?sh"
|
|
38
|
+
- "\\bnc\\b.*\\|\\s*/bin/(ba)?sh"
|
|
39
|
+
- "mkfifo\\s+/tmp/.*\\bnc\\b"
|
|
40
|
+
metadata:
|
|
41
|
+
category: reverse_shell
|
|
42
|
+
action: BLOCK
|
|
43
|
+
confidence: HIGH
|
|
44
|
+
malware_family: generic
|
|
45
|
+
|
|
46
|
+
- id: clawhavoc.revshell.python
|
|
47
|
+
severity: CRITICAL
|
|
48
|
+
message: "Python reverse shell detected — socket connect with dup2/subprocess"
|
|
49
|
+
patterns:
|
|
50
|
+
- "socket\\.connect\\s*\\(\\s*\\([\"']\\d+\\.\\d+\\.\\d+\\.\\d+[\"']"
|
|
51
|
+
- "os\\.dup2\\s*\\(\\s*s\\.fileno\\(\\)"
|
|
52
|
+
- "subprocess\\.call\\s*\\(\\s*\\[.*?/bin/(ba)?sh.*?\\].*?stdin\\s*=\\s*s"
|
|
53
|
+
- "socket\\.socket\\(.*?SOCK_STREAM\\).*?\\.connect\\(.*?\\).*?os\\.dup2"
|
|
54
|
+
metadata:
|
|
55
|
+
category: reverse_shell
|
|
56
|
+
action: BLOCK
|
|
57
|
+
confidence: HIGH
|
|
58
|
+
malware_family: generic
|
|
59
|
+
|
|
60
|
+
- id: clawhavoc.revshell.perl
|
|
61
|
+
severity: CRITICAL
|
|
62
|
+
message: "Perl reverse shell detected — socket INET with exec"
|
|
63
|
+
patterns:
|
|
64
|
+
- "perl\\s+-e\\s+.*socket\\s*\\(\\s*S.*INET"
|
|
65
|
+
- "perl\\s+-e\\s+.*exec\\s*\\(.*?/bin/(ba)?sh"
|
|
66
|
+
- "IO::Socket::INET.*?\\bexec\\b.*?/bin/(ba)?sh"
|
|
67
|
+
- "perl\\s+.*?socket.*?connect.*?open.*?STDIN"
|
|
68
|
+
metadata:
|
|
69
|
+
category: reverse_shell
|
|
70
|
+
action: BLOCK
|
|
71
|
+
confidence: HIGH
|
|
72
|
+
malware_family: generic
|
|
73
|
+
|
|
74
|
+
- id: clawhavoc.revshell.ruby
|
|
75
|
+
severity: CRITICAL
|
|
76
|
+
message: "Ruby reverse shell detected — TCPSocket with exec"
|
|
77
|
+
patterns:
|
|
78
|
+
- "TCPSocket\\.(new|open)\\s*\\(.*?\\d+\\.\\d+\\.\\d+\\.\\d+"
|
|
79
|
+
- "TCPSocket\\.open.*?exec\\s+.*?/bin/(ba)?sh"
|
|
80
|
+
- "require\\s+[\"']socket[\"'].*?TCPSocket.*?exec"
|
|
81
|
+
metadata:
|
|
82
|
+
category: reverse_shell
|
|
83
|
+
action: BLOCK
|
|
84
|
+
confidence: HIGH
|
|
85
|
+
malware_family: generic
|
|
86
|
+
|
|
87
|
+
# ==========================================================================
|
|
88
|
+
# CATEGORY 2: CRYPTO MINERS
|
|
89
|
+
# ==========================================================================
|
|
90
|
+
|
|
91
|
+
- id: clawhavoc.miner.xmrig
|
|
92
|
+
severity: CRITICAL
|
|
93
|
+
message: "XMRig crypto miner detected — mines Monero using victim resources"
|
|
94
|
+
patterns:
|
|
95
|
+
- "\\bxmrig\\b"
|
|
96
|
+
- "stratum\\+tcp://"
|
|
97
|
+
- "pool\\.minexmr\\.com"
|
|
98
|
+
- "pool\\.hashvault\\.pro"
|
|
99
|
+
- "\\bRandomX\\b.*?--donate-level"
|
|
100
|
+
metadata:
|
|
101
|
+
category: crypto_miner
|
|
102
|
+
action: BLOCK
|
|
103
|
+
confidence: HIGH
|
|
104
|
+
malware_family: xmrig
|
|
105
|
+
|
|
106
|
+
- id: clawhavoc.miner.coinhive
|
|
107
|
+
severity: CRITICAL
|
|
108
|
+
message: "Browser-based crypto miner detected — mines via WebAssembly in browser"
|
|
109
|
+
patterns:
|
|
110
|
+
- "\\bcoinhive\\b"
|
|
111
|
+
- "CoinHive\\.Anonymous"
|
|
112
|
+
- "\\bwebminepool\\b"
|
|
113
|
+
- "coin-hive\\.com/lib"
|
|
114
|
+
- "\\bCryptoLoot\\b"
|
|
115
|
+
metadata:
|
|
116
|
+
category: crypto_miner
|
|
117
|
+
action: BLOCK
|
|
118
|
+
confidence: HIGH
|
|
119
|
+
malware_family: coinhive
|
|
120
|
+
|
|
121
|
+
# ==========================================================================
|
|
122
|
+
# CATEGORY 3: INFO STEALERS
|
|
123
|
+
# ==========================================================================
|
|
124
|
+
|
|
125
|
+
- id: clawhavoc.stealer.browser-cookies
|
|
126
|
+
severity: CRITICAL
|
|
127
|
+
message: "Browser cookie/credential theft detected — reads stored login data"
|
|
128
|
+
patterns:
|
|
129
|
+
- "Chrome/User\\s*Data/Default/Cookies"
|
|
130
|
+
- "Firefox/Profiles/.*?cookies\\.sqlite"
|
|
131
|
+
- "Chrome/User\\s*Data/Default/Login\\s*Data"
|
|
132
|
+
- "Google/Chrome.*?(Cookies|Login\\s*Data)"
|
|
133
|
+
- "Application\\s*Support/(Google/Chrome|Firefox).*?(cookies|login)"
|
|
134
|
+
metadata:
|
|
135
|
+
category: info_stealer
|
|
136
|
+
action: BLOCK
|
|
137
|
+
confidence: HIGH
|
|
138
|
+
malware_family: generic
|
|
139
|
+
|
|
140
|
+
- id: clawhavoc.stealer.keychain
|
|
141
|
+
severity: CRITICAL
|
|
142
|
+
message: "macOS Keychain theft detected — dumps stored credentials"
|
|
143
|
+
patterns:
|
|
144
|
+
- "security\\s+find-generic-password"
|
|
145
|
+
- "security\\s+find-internet-password"
|
|
146
|
+
- "security\\s+dump-keychain"
|
|
147
|
+
- "security\\s+export\\s+-k\\s+.*?keychain"
|
|
148
|
+
metadata:
|
|
149
|
+
category: info_stealer
|
|
150
|
+
action: BLOCK
|
|
151
|
+
confidence: HIGH
|
|
152
|
+
malware_family: generic
|
|
153
|
+
|
|
154
|
+
- id: clawhavoc.stealer.atomic
|
|
155
|
+
severity: CRITICAL
|
|
156
|
+
message: "Atomic Stealer pattern detected — osascript password prompt"
|
|
157
|
+
patterns:
|
|
158
|
+
- "osascript.*?display\\s+dialog.*?password"
|
|
159
|
+
- "osascript.*?display\\s+dialog.*?with\\s+hidden\\s+answer"
|
|
160
|
+
- "osascript\\s+-e.*?text\\s+returned.*?password"
|
|
161
|
+
metadata:
|
|
162
|
+
category: info_stealer
|
|
163
|
+
action: BLOCK
|
|
164
|
+
confidence: HIGH
|
|
165
|
+
malware_family: atomic_stealer
|
|
166
|
+
campaign: macos_atomic
|
|
167
|
+
|
|
168
|
+
- id: clawhavoc.stealer.redline
|
|
169
|
+
severity: CRITICAL
|
|
170
|
+
message: "RedLine stealer pattern detected — targets browser and system credentials"
|
|
171
|
+
patterns:
|
|
172
|
+
- "\\\\Browsers\\\\.*?Login\\s*Data"
|
|
173
|
+
- "\\bvaultcli\\.dll\\b"
|
|
174
|
+
- "\\bCredentialManager\\b"
|
|
175
|
+
- "VaultOpenVault|VaultEnumerateItems"
|
|
176
|
+
- "\\\\User\\s*Data\\\\.*?(Web\\s*Data|Login\\s*Data)"
|
|
177
|
+
metadata:
|
|
178
|
+
category: info_stealer
|
|
179
|
+
action: BLOCK
|
|
180
|
+
confidence: HIGH
|
|
181
|
+
malware_family: redline
|
|
182
|
+
|
|
183
|
+
- id: clawhavoc.stealer.lumma
|
|
184
|
+
severity: CRITICAL
|
|
185
|
+
message: "Lumma/wallet stealer pattern detected — targets crypto wallets"
|
|
186
|
+
patterns:
|
|
187
|
+
- "\\bwallet\\.dat\\b"
|
|
188
|
+
- "Exodus.*?passphrase"
|
|
189
|
+
- "metamask.*?vault"
|
|
190
|
+
- "\\bsollet\\b.*?keystore"
|
|
191
|
+
- "Ethereum/keystore"
|
|
192
|
+
metadata:
|
|
193
|
+
category: info_stealer
|
|
194
|
+
action: BLOCK
|
|
195
|
+
confidence: MEDIUM
|
|
196
|
+
malware_family: lumma
|
|
197
|
+
|
|
198
|
+
# ==========================================================================
|
|
199
|
+
# CATEGORY 4: KEYLOGGERS
|
|
200
|
+
# ==========================================================================
|
|
201
|
+
|
|
202
|
+
- id: clawhavoc.keylogger.macos
|
|
203
|
+
severity: CRITICAL
|
|
204
|
+
message: "macOS keylogger detected — captures keystrokes via system APIs"
|
|
205
|
+
patterns:
|
|
206
|
+
- "\\bCGEventTapCreate\\b"
|
|
207
|
+
- "\\bkCGEventKeyDown\\b"
|
|
208
|
+
- "NSEvent\\.addGlobalMonitor"
|
|
209
|
+
- "CGEventMaskBit\\(.*?kCGEventKeyDown"
|
|
210
|
+
metadata:
|
|
211
|
+
category: keylogger
|
|
212
|
+
action: BLOCK
|
|
213
|
+
confidence: HIGH
|
|
214
|
+
malware_family: generic
|
|
215
|
+
|
|
216
|
+
- id: clawhavoc.keylogger.generic
|
|
217
|
+
severity: HIGH
|
|
218
|
+
message: "Generic keylogger pattern detected — captures keyboard input"
|
|
219
|
+
patterns:
|
|
220
|
+
- "\\bkeylogger\\b"
|
|
221
|
+
- "pynput\\.keyboard\\.Listener"
|
|
222
|
+
- "addEventListener\\s*\\(\\s*[\"']keydown[\"']"
|
|
223
|
+
- "on_press.*?keyboard.*?listener"
|
|
224
|
+
- "SetWindowsHookEx.*?WH_KEYBOARD"
|
|
225
|
+
metadata:
|
|
226
|
+
category: keylogger
|
|
227
|
+
action: BLOCK
|
|
228
|
+
confidence: MEDIUM
|
|
229
|
+
malware_family: generic
|
|
230
|
+
|
|
231
|
+
# ==========================================================================
|
|
232
|
+
# CATEGORY 5: SCREEN CAPTURE
|
|
233
|
+
# ==========================================================================
|
|
234
|
+
|
|
235
|
+
- id: clawhavoc.screencap.capture-upload
|
|
236
|
+
severity: HIGH
|
|
237
|
+
message: "Screenshot capture with upload detected — exfiltrates screen content"
|
|
238
|
+
patterns:
|
|
239
|
+
- "screenshot.*?upload"
|
|
240
|
+
- "screencapture.*?curl"
|
|
241
|
+
- "ImageGrab.*?(send|post|upload|requests)"
|
|
242
|
+
- "pyautogui\\.screenshot.*?(requests|urllib|http)"
|
|
243
|
+
- "screen\\s*capture.*?(webhook|http|curl)"
|
|
244
|
+
metadata:
|
|
245
|
+
category: screen_capture
|
|
246
|
+
action: BLOCK
|
|
247
|
+
confidence: MEDIUM
|
|
248
|
+
malware_family: generic
|
|
249
|
+
|
|
250
|
+
# ==========================================================================
|
|
251
|
+
# CATEGORY 6: DNS EXFILTRATION
|
|
252
|
+
# ==========================================================================
|
|
253
|
+
|
|
254
|
+
- id: clawhavoc.exfil.dns
|
|
255
|
+
severity: CRITICAL
|
|
256
|
+
message: "DNS exfiltration detected — encodes data in DNS queries"
|
|
257
|
+
patterns:
|
|
258
|
+
- "nslookup\\s+\\$\\("
|
|
259
|
+
- "dig\\s+\\$\\("
|
|
260
|
+
- "base64.*?nslookup"
|
|
261
|
+
- "nslookup.*?base64"
|
|
262
|
+
- "\\$\\(.*?\\)\\..*?\\.\\w{2,6}\\b"
|
|
263
|
+
metadata:
|
|
264
|
+
category: dns_exfiltration
|
|
265
|
+
action: BLOCK
|
|
266
|
+
confidence: HIGH
|
|
267
|
+
malware_family: generic
|
|
268
|
+
|
|
269
|
+
# ==========================================================================
|
|
270
|
+
# CATEGORY 7: C2 BEACONS
|
|
271
|
+
# ==========================================================================
|
|
272
|
+
|
|
273
|
+
- id: clawhavoc.c2.periodic-http
|
|
274
|
+
severity: CRITICAL
|
|
275
|
+
message: "C2 beacon detected — periodic HTTP callbacks for command and control"
|
|
276
|
+
patterns:
|
|
277
|
+
- "setInterval\\s*\\(.*?fetch\\s*\\("
|
|
278
|
+
- "while\\s+(True|true|1).*?requests\\.(get|post).*?sleep"
|
|
279
|
+
- "setInterval\\s*\\(.*?(axios|http|XMLHttpRequest)"
|
|
280
|
+
- "schedule\\.every\\(.*?\\)\\.(get|post|fetch)"
|
|
281
|
+
metadata:
|
|
282
|
+
category: c2_beacon
|
|
283
|
+
action: BLOCK
|
|
284
|
+
confidence: HIGH
|
|
285
|
+
malware_family: generic
|
|
286
|
+
|
|
287
|
+
- id: clawhavoc.c2.encoded-payload
|
|
288
|
+
severity: CRITICAL
|
|
289
|
+
message: "Encoded payload execution detected — decodes and executes hidden code"
|
|
290
|
+
patterns:
|
|
291
|
+
- "eval\\s*\\(\\s*atob\\s*\\("
|
|
292
|
+
- "exec\\s*\\(\\s*base64\\.b64decode\\s*\\("
|
|
293
|
+
- "eval\\s*\\(\\s*Buffer\\.from\\s*\\(.*?[\"']base64[\"']"
|
|
294
|
+
- "new\\s+Function\\s*\\(\\s*atob\\s*\\("
|
|
295
|
+
- "exec\\s*\\(\\s*codecs\\.decode\\s*\\("
|
|
296
|
+
metadata:
|
|
297
|
+
category: c2_beacon
|
|
298
|
+
action: BLOCK
|
|
299
|
+
confidence: HIGH
|
|
300
|
+
malware_family: generic
|
|
301
|
+
|
|
302
|
+
# ==========================================================================
|
|
303
|
+
# CATEGORY 8: OPENCLAW-SPECIFIC ATTACKS
|
|
304
|
+
# ==========================================================================
|
|
305
|
+
|
|
306
|
+
- id: clawhavoc.openclaw.soul-tampering
|
|
307
|
+
severity: CRITICAL
|
|
308
|
+
message: "SOUL.md tampering detected — modifies the agent's core identity/instructions"
|
|
309
|
+
patterns:
|
|
310
|
+
- "(write|echo|cat|tee)\\s+.*?>\\s*SOUL\\.md"
|
|
311
|
+
- "modify\\s+.*?SOUL\\.md"
|
|
312
|
+
- "sed\\s+-i.*?SOUL\\.md"
|
|
313
|
+
- "echo\\s+.*?>>\\s*SOUL\\.md"
|
|
314
|
+
- "(open|fs\\.writeFile).*?SOUL\\.md"
|
|
315
|
+
metadata:
|
|
316
|
+
category: openclaw_attack
|
|
317
|
+
action: BLOCK
|
|
318
|
+
confidence: HIGH
|
|
319
|
+
malware_family: openclaw_specific
|
|
320
|
+
|
|
321
|
+
- id: clawhavoc.openclaw.config-theft
|
|
322
|
+
severity: CRITICAL
|
|
323
|
+
message: "OpenClaw config theft detected — reads private agent configuration"
|
|
324
|
+
patterns:
|
|
325
|
+
- "~/\\.openclaw/"
|
|
326
|
+
- "\\$HOME/\\.openclaw"
|
|
327
|
+
- "cat\\s+.*?\\.openclaw"
|
|
328
|
+
- "read.*?\\.openclaw/config"
|
|
329
|
+
- "\\.openclaw/(credentials|tokens?|auth)"
|
|
330
|
+
metadata:
|
|
331
|
+
category: openclaw_attack
|
|
332
|
+
action: BLOCK
|
|
333
|
+
confidence: HIGH
|
|
334
|
+
malware_family: openclaw_specific
|
|
335
|
+
|
|
336
|
+
- id: clawhavoc.openclaw.session-hijack
|
|
337
|
+
severity: CRITICAL
|
|
338
|
+
message: "OpenClaw session hijack detected — steals authentication tokens"
|
|
339
|
+
patterns:
|
|
340
|
+
- "openclaw\\s+session\\s+token"
|
|
341
|
+
- "openclaw.*?auth\\s*cookie"
|
|
342
|
+
- "OPENCLAW_SESSION"
|
|
343
|
+
- "openclaw.*?bearer\\s+token"
|
|
344
|
+
- "steal.*?openclaw.*?(session|token|auth)"
|
|
345
|
+
metadata:
|
|
346
|
+
category: openclaw_attack
|
|
347
|
+
action: BLOCK
|
|
348
|
+
confidence: HIGH
|
|
349
|
+
malware_family: openclaw_specific
|
|
350
|
+
|
|
351
|
+
- id: clawhavoc.openclaw.skill-mutation
|
|
352
|
+
severity: CRITICAL
|
|
353
|
+
message: "SKILL.md self-mutation detected — skill modifies its own definition"
|
|
354
|
+
patterns:
|
|
355
|
+
- "(write|echo|cat|tee)\\s+.*?>\\s*SKILL\\.md"
|
|
356
|
+
- "echo\\s+.*?>>\\s*SKILL\\.md"
|
|
357
|
+
- "sed\\s+-i.*?SKILL\\.md"
|
|
358
|
+
- "modify\\s+.*?SKILL\\.md"
|
|
359
|
+
- "(open|fs\\.writeFile).*?SKILL\\.md"
|
|
360
|
+
metadata:
|
|
361
|
+
category: openclaw_attack
|
|
362
|
+
action: BLOCK
|
|
363
|
+
confidence: HIGH
|
|
364
|
+
malware_family: openclaw_specific
|
|
365
|
+
|
|
366
|
+
- id: clawhavoc.openclaw.gateway-token
|
|
367
|
+
severity: CRITICAL
|
|
368
|
+
message: "OpenClaw gateway token theft detected — targets API authentication"
|
|
369
|
+
patterns:
|
|
370
|
+
- "OPENCLAW_GATEWAY_TOKEN"
|
|
371
|
+
- "OPENCLAW_API_KEY"
|
|
372
|
+
- "OPENCLAW_SECRET"
|
|
373
|
+
- "openclaw.*?gateway.*?token"
|
|
374
|
+
- "export\\s+OPENCLAW_(GATEWAY_TOKEN|API_KEY)"
|
|
375
|
+
metadata:
|
|
376
|
+
category: openclaw_attack
|
|
377
|
+
action: BLOCK
|
|
378
|
+
confidence: HIGH
|
|
379
|
+
malware_family: openclaw_specific
|
|
380
|
+
|
|
381
|
+
# ==========================================================================
|
|
382
|
+
# CATEGORY 9: CAMPAIGN PATTERNS
|
|
383
|
+
# ==========================================================================
|
|
384
|
+
|
|
385
|
+
- id: clawhavoc.campaign.osascript-dialog
|
|
386
|
+
severity: HIGH
|
|
387
|
+
message: "osascript dialog social engineering — tricks user into entering credentials"
|
|
388
|
+
patterns:
|
|
389
|
+
- "osascript.*?display\\s+dialog"
|
|
390
|
+
- "osascript\\s+-e\\s+.*?display\\s+alert"
|
|
391
|
+
- "osascript.*?text\\s+returned"
|
|
392
|
+
metadata:
|
|
393
|
+
category: campaign
|
|
394
|
+
action: WARN
|
|
395
|
+
confidence: MEDIUM
|
|
396
|
+
campaign: macos_social_engineering
|
|
397
|
+
|
|
398
|
+
- id: clawhavoc.campaign.xattr-quarantine
|
|
399
|
+
severity: CRITICAL
|
|
400
|
+
message: "Quarantine bypass detected — removes macOS Gatekeeper protection"
|
|
401
|
+
patterns:
|
|
402
|
+
- "xattr\\s+-d\\s+com\\.apple\\.quarantine"
|
|
403
|
+
- "xattr\\s+-c\\s+com\\.apple\\.quarantine"
|
|
404
|
+
- "xattr\\s+-r\\s+-d.*?quarantine"
|
|
405
|
+
- "spctl\\s+--master-disable"
|
|
406
|
+
metadata:
|
|
407
|
+
category: campaign
|
|
408
|
+
action: BLOCK
|
|
409
|
+
confidence: HIGH
|
|
410
|
+
campaign: macos_gatekeeper_bypass
|
|
411
|
+
|
|
412
|
+
- id: clawhavoc.campaign.clickfix
|
|
413
|
+
severity: CRITICAL
|
|
414
|
+
message: "ClickFix social engineering detected — instructs user to paste commands"
|
|
415
|
+
patterns:
|
|
416
|
+
- "press\\s+(Win|Windows|Cmd|Command)\\s*\\+\\s*R.*?paste"
|
|
417
|
+
- "open\\s+(terminal|cmd|powershell).*?paste\\s+(the\\s+)?following"
|
|
418
|
+
- "copy\\s+and\\s+paste\\s+(this|the\\s+following)\\s+(into|in)\\s+(terminal|cmd|powershell|shell)"
|
|
419
|
+
- "run\\s+this\\s+(in|from)\\s+(your\\s+)?(terminal|command\\s+prompt)"
|
|
420
|
+
metadata:
|
|
421
|
+
category: campaign
|
|
422
|
+
action: BLOCK
|
|
423
|
+
confidence: HIGH
|
|
424
|
+
campaign: clickfix
|
|
425
|
+
|
|
426
|
+
# ==========================================================================
|
|
427
|
+
# CATEGORY 10: EXFIL ENDPOINTS
|
|
428
|
+
# ==========================================================================
|
|
429
|
+
|
|
430
|
+
- id: clawhavoc.campaign.webhook-exfil
|
|
431
|
+
severity: CRITICAL
|
|
432
|
+
message: "Known exfiltration endpoint detected — data sent to attacker-controlled webhook"
|
|
433
|
+
patterns:
|
|
434
|
+
- "webhook\\.site"
|
|
435
|
+
- "discord\\.com/api/webhooks"
|
|
436
|
+
- "hooks\\.slack\\.com"
|
|
437
|
+
- "pipedream\\.net"
|
|
438
|
+
- "requestbin\\.(com|net)"
|
|
439
|
+
metadata:
|
|
440
|
+
category: exfil_endpoint
|
|
441
|
+
action: BLOCK
|
|
442
|
+
confidence: HIGH
|
|
443
|
+
malware_family: generic
|
package/src/cli/audit.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/cli/audit.js — OpenClaw configuration security audit (stub)
|
|
2
|
+
|
|
3
|
+
export async function runAudit(args) {
|
|
4
|
+
console.log('\n Security Audit [EXPERIMENTAL STUB]\n');
|
|
5
|
+
console.log(' This command will check your OpenClaw configuration for security issues.');
|
|
6
|
+
console.log(' Full implementation coming in Sprint 3.\n');
|
|
7
|
+
console.log(' WARNING: This is an experimental stub. No actual checks are performed.\n');
|
|
8
|
+
console.log(' Planned checks (60+):');
|
|
9
|
+
console.log(' - Gateway: bind mode, auth, token strength, HTTPS, CORS');
|
|
10
|
+
console.log(' - Permissions: config files, credentials, session transcripts');
|
|
11
|
+
console.log(' - Tool Policy: allowlist, sandbox, elevated tools');
|
|
12
|
+
console.log(' - DM/Group: open access, pairing bypass, mention gating');
|
|
13
|
+
console.log(' - Hooks: weak tokens, unsafe external content');
|
|
14
|
+
console.log(' - mDNS: exposure, metadata leaks');
|
|
15
|
+
console.log(' - Plugins: unsigned, permissive, outdated');
|
|
16
|
+
console.log(' - Credentials: plaintext secrets, exposed API keys\n');
|
|
17
|
+
console.log(' OWASP ASI Top 10 mapping for all findings.\n');
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/cli/harden.js — OpenClaw auto-hardening (stub)
|
|
2
|
+
|
|
3
|
+
export async function runHarden(args) {
|
|
4
|
+
console.log('\n Auto-Hardening [EXPERIMENTAL STUB]\n');
|
|
5
|
+
console.log(' This command will automatically fix security issues in your OpenClaw config.');
|
|
6
|
+
console.log(' Full implementation coming in Sprint 3.\n');
|
|
7
|
+
console.log(' WARNING: This is an experimental stub. No actions are performed.\n');
|
|
8
|
+
console.log(' Planned actions:');
|
|
9
|
+
console.log(' - Bind gateway to 127.0.0.1');
|
|
10
|
+
console.log(' - Enable token authentication');
|
|
11
|
+
console.log(' - Set config file permissions to 600');
|
|
12
|
+
console.log(' - Disable mDNS discovery');
|
|
13
|
+
console.log(' - Remove plaintext credentials\n');
|
|
14
|
+
console.log(' Usage: agent-security-scanner-mcp harden --fix [--dry-run]\n');
|
|
15
|
+
}
|
package/src/context.js
CHANGED
|
@@ -163,7 +163,11 @@ const TEST_FILE_PATTERNS = [
|
|
|
163
163
|
/[._](?:test|spec)\.[^.]+$/i,
|
|
164
164
|
/[/\\]test[-_]?files?[/\\]/i,
|
|
165
165
|
/[/\\]fixtures?[/\\]/i,
|
|
166
|
+
/[/\\]__fixtures__[/\\]/i,
|
|
167
|
+
/[/\\]__mocks__[/\\]/i,
|
|
168
|
+
/[/\\]mocks?[/\\]/i,
|
|
166
169
|
/[/\\]demo[/\\]/i,
|
|
170
|
+
/[/\\]test[-_][^/\\]+\.[^.]+$/i,
|
|
167
171
|
];
|
|
168
172
|
|
|
169
173
|
// Check if a file path looks like a test file
|
package/src/daemon-client.js
CHANGED
|
@@ -204,6 +204,16 @@ class DaemonClient {
|
|
|
204
204
|
return resp.result;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
async preWarm() {
|
|
208
|
+
if (this._dead) return;
|
|
209
|
+
try {
|
|
210
|
+
await this.ensureRunning();
|
|
211
|
+
await this.health();
|
|
212
|
+
} catch {
|
|
213
|
+
// Pre-warm failure is non-fatal
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
207
217
|
async shutdown() {
|
|
208
218
|
if (!this._proc || this._proc.killed || this._proc.exitCode !== null) return;
|
|
209
219
|
try {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/plugin-config.js — Plugin configuration loader
|
|
2
|
+
// Loads config from ~/.openclaw/scanner-config.json, .scannerrc.json, or .clawproofrc.json
|
|
3
|
+
|
|
4
|
+
import { existsSync, readFileSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_PLUGIN_CONFIG = {
|
|
9
|
+
version: 1,
|
|
10
|
+
features: {
|
|
11
|
+
scan_on_write: true,
|
|
12
|
+
scan_on_skill_install: true,
|
|
13
|
+
prompt_firewall: true,
|
|
14
|
+
package_hallucination: true,
|
|
15
|
+
config_audit: true,
|
|
16
|
+
},
|
|
17
|
+
severity_threshold: 'warning',
|
|
18
|
+
auto_block: true,
|
|
19
|
+
output_format: 'json',
|
|
20
|
+
daemon: {
|
|
21
|
+
prewarm: true,
|
|
22
|
+
cache_size: 200,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function loadPluginConfig() {
|
|
27
|
+
const paths = [
|
|
28
|
+
join(homedir(), '.openclaw', 'scanner-config.json'),
|
|
29
|
+
join(process.cwd(), '.clawproofrc.json'),
|
|
30
|
+
join(process.cwd(), '.scannerrc.json'),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const configPath of paths) {
|
|
34
|
+
if (existsSync(configPath)) {
|
|
35
|
+
try {
|
|
36
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
37
|
+
const parsed = JSON.parse(raw);
|
|
38
|
+
|
|
39
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const parsedFeatures = parsed.features && typeof parsed.features === 'object' ? parsed.features : {};
|
|
44
|
+
const parsedDaemon = parsed.daemon && typeof parsed.daemon === 'object' ? parsed.daemon : {};
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
version: typeof parsed.version === 'number' ? parsed.version : DEFAULT_PLUGIN_CONFIG.version,
|
|
48
|
+
features: {
|
|
49
|
+
scan_on_write: parsedFeatures.scan_on_write ?? DEFAULT_PLUGIN_CONFIG.features.scan_on_write,
|
|
50
|
+
scan_on_skill_install: parsedFeatures.scan_on_skill_install ?? DEFAULT_PLUGIN_CONFIG.features.scan_on_skill_install,
|
|
51
|
+
prompt_firewall: parsedFeatures.prompt_firewall ?? DEFAULT_PLUGIN_CONFIG.features.prompt_firewall,
|
|
52
|
+
package_hallucination: parsedFeatures.package_hallucination ?? DEFAULT_PLUGIN_CONFIG.features.package_hallucination,
|
|
53
|
+
config_audit: parsedFeatures.config_audit ?? DEFAULT_PLUGIN_CONFIG.features.config_audit,
|
|
54
|
+
},
|
|
55
|
+
severity_threshold: ['info', 'warning', 'error'].includes(parsed.severity_threshold)
|
|
56
|
+
? parsed.severity_threshold : DEFAULT_PLUGIN_CONFIG.severity_threshold,
|
|
57
|
+
auto_block: typeof parsed.auto_block === 'boolean' ? parsed.auto_block : DEFAULT_PLUGIN_CONFIG.auto_block,
|
|
58
|
+
output_format: ['json', 'text'].includes(parsed.output_format)
|
|
59
|
+
? parsed.output_format : DEFAULT_PLUGIN_CONFIG.output_format,
|
|
60
|
+
daemon: {
|
|
61
|
+
prewarm: parsedDaemon.prewarm ?? DEFAULT_PLUGIN_CONFIG.daemon.prewarm,
|
|
62
|
+
cache_size: typeof parsedDaemon.cache_size === 'number' ? parsedDaemon.cache_size : DEFAULT_PLUGIN_CONFIG.daemon.cache_size,
|
|
63
|
+
},
|
|
64
|
+
_source: configPath,
|
|
65
|
+
};
|
|
66
|
+
} catch {
|
|
67
|
+
// Malformed config, fall through
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { ...DEFAULT_PLUGIN_CONFIG, _source: null };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getDefaultPluginConfig() {
|
|
76
|
+
return { ...DEFAULT_PLUGIN_CONFIG };
|
|
77
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/plugin-health.js — Plugin health check endpoint
|
|
2
|
+
|
|
3
|
+
import { getDaemonClient } from './daemon-client.js';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { join, dirname } from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
|
|
8
|
+
let __dirname;
|
|
9
|
+
try {
|
|
10
|
+
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
} catch {
|
|
12
|
+
__dirname = process.cwd();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function getHealthStatus() {
|
|
16
|
+
let version = '0.0.0';
|
|
17
|
+
try {
|
|
18
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
19
|
+
version = pkg.version || '0.0.0';
|
|
20
|
+
} catch { /* ignore */ }
|
|
21
|
+
|
|
22
|
+
let daemonHealth = null;
|
|
23
|
+
try {
|
|
24
|
+
const client = getDaemonClient();
|
|
25
|
+
if (client.isAvailable) {
|
|
26
|
+
daemonHealth = await client.health();
|
|
27
|
+
}
|
|
28
|
+
} catch { /* daemon not available */ }
|
|
29
|
+
|
|
30
|
+
// Try to get package stats without failing
|
|
31
|
+
let packageStats = null;
|
|
32
|
+
try {
|
|
33
|
+
const { getPackageStats } = await import('./tools/check-package.js');
|
|
34
|
+
packageStats = getPackageStats();
|
|
35
|
+
} catch { /* packages not loaded */ }
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
name: 'agent-security-scanner-mcp',
|
|
39
|
+
version,
|
|
40
|
+
daemon: daemonHealth ? {
|
|
41
|
+
status: 'running',
|
|
42
|
+
pid: daemonHealth.pid,
|
|
43
|
+
cache_size: daemonHealth.cache_size,
|
|
44
|
+
uptime_seconds: Math.round(daemonHealth.uptime),
|
|
45
|
+
} : { status: 'not running' },
|
|
46
|
+
packages: packageStats,
|
|
47
|
+
timestamp: new Date().toISOString(),
|
|
48
|
+
};
|
|
49
|
+
}
|