mohuclaw 1.0.1

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 (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/bin/mohu-tui.js +73 -0
  4. package/bin/mohu-webui.js +67 -0
  5. package/dist/tui/tui.js +38733 -0
  6. package/dist/webui/index.html +1551 -0
  7. package/dist/webui/server.js +876 -0
  8. package/ioc/c2-ips.txt +25 -0
  9. package/ioc/file-hashes.txt +13 -0
  10. package/ioc/malicious-domains.txt +46 -0
  11. package/ioc/malicious-hashes.txt +5 -0
  12. package/ioc/malicious-publishers.txt +34 -0
  13. package/ioc/malicious-skill-patterns.txt +87 -0
  14. package/package.json +46 -0
  15. package/scripts/check/access_control.sh +183 -0
  16. package/scripts/check/credential_storage.sh +222 -0
  17. package/scripts/check/execution_sandbox.sh +502 -0
  18. package/scripts/check/memory_poisoning.sh +334 -0
  19. package/scripts/check/network_exposure.sh +479 -0
  20. package/scripts/check/resource_cost.sh +182 -0
  21. package/scripts/check/supply_chain.sh +553 -0
  22. package/scripts/repair/access_control/_common.sh +249 -0
  23. package/scripts/repair/access_control/check_1.sh +28 -0
  24. package/scripts/repair/access_control/check_2.sh +27 -0
  25. package/scripts/repair/access_control/check_3.sh +23 -0
  26. package/scripts/repair/access_control/check_4.sh +23 -0
  27. package/scripts/repair/access_control/check_5.sh +20 -0
  28. package/scripts/repair/credential_storage/_common.sh +277 -0
  29. package/scripts/repair/credential_storage/check_1.sh +47 -0
  30. package/scripts/repair/credential_storage/check_2.sh +35 -0
  31. package/scripts/repair/credential_storage/check_3.sh +53 -0
  32. package/scripts/repair/credential_storage/logs/security-scan.log +15 -0
  33. package/scripts/repair/execution_sandbox/_common.sh +302 -0
  34. package/scripts/repair/execution_sandbox/check_1.sh +67 -0
  35. package/scripts/repair/execution_sandbox/check_10.sh +23 -0
  36. package/scripts/repair/execution_sandbox/check_11.sh +34 -0
  37. package/scripts/repair/execution_sandbox/check_12.sh +38 -0
  38. package/scripts/repair/execution_sandbox/check_13.sh +29 -0
  39. package/scripts/repair/execution_sandbox/check_2.sh +46 -0
  40. package/scripts/repair/execution_sandbox/check_3.sh +37 -0
  41. package/scripts/repair/execution_sandbox/check_4.sh +23 -0
  42. package/scripts/repair/execution_sandbox/check_5.sh +28 -0
  43. package/scripts/repair/execution_sandbox/check_6.sh +17 -0
  44. package/scripts/repair/execution_sandbox/check_7.sh +17 -0
  45. package/scripts/repair/execution_sandbox/check_8.sh +17 -0
  46. package/scripts/repair/execution_sandbox/check_9.sh +17 -0
  47. package/scripts/repair/execution_sandbox/logs/security-scan.log +10 -0
  48. package/scripts/repair/memory_poisoning/_common.sh +336 -0
  49. package/scripts/repair/memory_poisoning/check_1.sh +51 -0
  50. package/scripts/repair/memory_poisoning/check_2.sh +26 -0
  51. package/scripts/repair/memory_poisoning/check_3.sh +24 -0
  52. package/scripts/repair/memory_poisoning/check_4.sh +27 -0
  53. package/scripts/repair/memory_poisoning/check_5.sh +20 -0
  54. package/scripts/repair/network_exposure/_common.sh +330 -0
  55. package/scripts/repair/network_exposure/check_1.sh +86 -0
  56. package/scripts/repair/network_exposure/check_10.sh +16 -0
  57. package/scripts/repair/network_exposure/check_11.sh +31 -0
  58. package/scripts/repair/network_exposure/check_12.sh +24 -0
  59. package/scripts/repair/network_exposure/check_2.sh +26 -0
  60. package/scripts/repair/network_exposure/check_3.sh +43 -0
  61. package/scripts/repair/network_exposure/check_4.sh +23 -0
  62. package/scripts/repair/network_exposure/check_5.sh +16 -0
  63. package/scripts/repair/network_exposure/check_6.sh +98 -0
  64. package/scripts/repair/network_exposure/check_7.sh +35 -0
  65. package/scripts/repair/network_exposure/check_8.sh +19 -0
  66. package/scripts/repair/network_exposure/check_9.sh +19 -0
  67. package/scripts/repair/resource_cost/_common.sh +303 -0
  68. package/scripts/repair/resource_cost/check_1.sh +16 -0
  69. package/scripts/repair/resource_cost/check_2.sh +16 -0
  70. package/scripts/repair/resource_cost/check_3.sh +23 -0
  71. package/scripts/repair/supply_chain/_common.sh +222 -0
  72. package/scripts/repair/supply_chain/check_1.sh +95 -0
  73. package/scripts/repair/supply_chain/check_10.sh +60 -0
  74. package/scripts/repair/supply_chain/check_11.sh +63 -0
  75. package/scripts/repair/supply_chain/check_12.sh +36 -0
  76. package/scripts/repair/supply_chain/check_13.sh +44 -0
  77. package/scripts/repair/supply_chain/check_14.sh +33 -0
  78. package/scripts/repair/supply_chain/check_15.sh +33 -0
  79. package/scripts/repair/supply_chain/check_16.sh +34 -0
  80. package/scripts/repair/supply_chain/check_17.sh +61 -0
  81. package/scripts/repair/supply_chain/check_18.sh +62 -0
  82. package/scripts/repair/supply_chain/check_2.sh +93 -0
  83. package/scripts/repair/supply_chain/check_3.sh +78 -0
  84. package/scripts/repair/supply_chain/check_4.sh +72 -0
  85. package/scripts/repair/supply_chain/check_5.sh +73 -0
  86. package/scripts/repair/supply_chain/check_6.sh +81 -0
  87. package/scripts/repair/supply_chain/check_7.sh +52 -0
  88. package/scripts/repair/supply_chain/check_8.sh +71 -0
  89. package/scripts/repair/supply_chain/check_9.sh +78 -0
  90. package/scripts/repair/supply_chain/logs/security-scan.log +77 -0
  91. package/scripts/scan.sh +228 -0
  92. package/webui/index.html +1551 -0
@@ -0,0 +1,553 @@
1
+ # shellcheck shell=bash
2
+
3
+ # ------------------------------------------------------------
4
+ # Shared helpers
5
+ # ------------------------------------------------------------
6
+
7
+ sha256_file() {
8
+ # Cross-platform SHA-256 for a file
9
+ local target="$1"
10
+ [ -f "$target" ] || return 1
11
+
12
+ if command -v sha256sum >/dev/null 2>&1; then
13
+ sha256sum "$target" 2>/dev/null | awk '{print tolower($1)}'
14
+ elif command -v shasum >/dev/null 2>&1; then
15
+ shasum -a 256 "$target" 2>/dev/null | awk '{print tolower($1)}'
16
+ elif command -v openssl >/dev/null 2>&1; then
17
+ openssl dgst -sha256 "$target" 2>/dev/null | sed -E 's/^.*= //' | tr '[:upper:]' '[:lower:]'
18
+ else
19
+ return 1
20
+ fi
21
+ }
22
+
23
+ load_hash_iocs() {
24
+ # Expected format: <sha256>|<campaign>
25
+ # Similar to c2-ips.txt storage style
26
+ if [ -f "$IOC_DIR/malicious-hashes.txt" ]; then
27
+ grep -v '^#' "$IOC_DIR/malicious-hashes.txt" | grep -v '^$' || true
28
+ fi
29
+ }
30
+
31
+ lookup_malicious_hash_campaign() {
32
+ # Usage: lookup_malicious_hash_campaign <sha256>
33
+ local needle
34
+ needle="$(printf "%s" "${1:-}" | tr '[:upper:]' '[:lower:]')"
35
+ [ -n "$needle" ] || return 1
36
+
37
+ load_hash_iocs | while IFS='|' read -r hash_val campaign rest; do
38
+ hash_val="$(printf "%s" "$hash_val" | tr '[:upper:]' '[:lower:]')"
39
+ if [ "$hash_val" = "$needle" ]; then
40
+ printf "%s\n" "${campaign:-unknown}"
41
+ return 0
42
+ fi
43
+ done
44
+ }
45
+
46
+ extract_github_account_age_days() {
47
+ # Best-effort local metadata extraction only.
48
+ # We cannot reliably infer GitHub account age from filesystem alone unless
49
+ # the skill metadata explicitly records it.
50
+ local skill_dir="$1"
51
+ local val=""
52
+
53
+ for meta in "$skill_dir/package.json" "$skill_dir/config.json" "$skill_dir/SKILL.md"; do
54
+ [ -f "$meta" ] || continue
55
+
56
+ # JSON-ish keys
57
+ val="$(grep -iEo '"(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)"[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
58
+ if [ -n "$val" ]; then
59
+ printf "%s\n" "$val"
60
+ return 0
61
+ fi
62
+
63
+ # YAML / markdown frontmatter-ish keys
64
+ val="$(grep -iE '^(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
65
+ if [ -n "$val" ]; then
66
+ printf "%s\n" "$val"
67
+ return 0
68
+ fi
69
+ done
70
+
71
+ return 1
72
+ }
73
+
74
+ # ============================================================
75
+ # CHECK 1 (origin 1): Known C2 Infrastructure
76
+ # ============================================================
77
+ header 1 "Scanning for known C2 infrastructure..."
78
+
79
+ log "DEBUG: SKILLS_DIR=$SKILLS_DIR"
80
+
81
+ if [ -d "$SKILLS_DIR" ]; then
82
+ C2_PATTERN="$(load_ips | tr '\n' '|' | sed 's/|$//' | sed 's/\./\\./g')"
83
+ if [ -n "$C2_PATTERN" ]; then
84
+ C2_HITS="$(grep -rlE --exclude-dir="$SELF_DIR_NAME" "$C2_PATTERN" "$SKILLS_DIR" 2>/dev/null || true)"
85
+ if [ -n "$C2_HITS" ]; then
86
+ result_critical "Known C2 IP found in:"
87
+ log "$C2_HITS"
88
+ else
89
+ result_clean "No C2 IPs detected"
90
+ fi
91
+ else
92
+ result_clean "No C2 IPs detected"
93
+ fi
94
+ else
95
+ result_clean "Skills directory not found; skipped C2 scan"
96
+ fi
97
+
98
+ # ============================================================
99
+ # CHECK 2 (origin 2): AMOS Stealer / AuthTool Markers
100
+ # ============================================================
101
+ header 2 "Scanning for AMOS stealer / AuthTool markers..."
102
+
103
+ if [ -d "$SKILLS_DIR" ]; then
104
+ AMOS_PATTERN='authtool|atomic\.stealer|AMOS|NovaStealer|nova\.stealer|osascript.*password|osascript.*dialog|osascript.*keychain|Security\.framework.*Auth|openclaw-agent\.exe|openclaw-agent\.zip|openclawcli\.zip|AuthTool|Installer-Package'
105
+ AMOS_HITS="$(grep -rliE --exclude-dir="$SELF_DIR_NAME" "$AMOS_PATTERN" "$SKILLS_DIR" 2>/dev/null || true)"
106
+ if [ -n "$AMOS_HITS" ]; then
107
+ result_critical "AMOS/stealer markers found in:"
108
+ log "$AMOS_HITS"
109
+ else
110
+ result_clean "No stealer markers"
111
+ fi
112
+ else
113
+ result_clean "Skills directory not found; skipped stealer marker scan"
114
+ fi
115
+
116
+ # ============================================================
117
+ # CHECK 3 (origin 3): Reverse Shells & Backdoors
118
+ # ============================================================
119
+ header 3 "Scanning for reverse shells & backdoors..."
120
+
121
+ if [ -d "$SKILLS_DIR" ]; then
122
+ SHELL_PATTERN='nc -e|/dev/tcp/|mkfifo.*nc|bash -i >|socat.*exec|python.*socket.*connect|nohup.*bash.*tcp|perl.*socket.*INET|ruby.*TCPSocket|php.*fsockopen|lua.*socket\.tcp|xattr -[cr]|com\.apple\.quarantine'
123
+ SHELL_HITS="$(grep -rlinE --exclude-dir="$SELF_DIR_NAME" "$SHELL_PATTERN" "$SKILLS_DIR" 2>/dev/null || true)"
124
+ if [ -n "$SHELL_HITS" ]; then
125
+ result_critical "Reverse shell patterns found in:"
126
+ log "$SHELL_HITS"
127
+ else
128
+ result_clean "No reverse shells"
129
+ fi
130
+ else
131
+ result_clean "Skills directory not found; skipped reverse shell scan"
132
+ fi
133
+
134
+ # ============================================================
135
+ # CHECK 4 (origin 4): Credential Exfiltration Endpoints
136
+ # ============================================================
137
+ header 4 "Scanning for credential exfiltration endpoints..."
138
+
139
+ DOMAIN_PATTERN="$(load_domains | tr '\n' '|' | sed 's/|$//' | sed 's/\./\\./g')"
140
+ if [ -d "$SKILLS_DIR" ] && [ -n "$DOMAIN_PATTERN" ]; then
141
+ EXFIL_HITS="$(grep -rlinE --exclude-dir="$SELF_DIR_NAME" "$DOMAIN_PATTERN" "$SKILLS_DIR" 2>/dev/null || true)"
142
+ if [ -n "$EXFIL_HITS" ]; then
143
+ result_critical "Exfiltration endpoints found in:"
144
+ log "$EXFIL_HITS"
145
+ else
146
+ result_clean "No exfiltration endpoints"
147
+ fi
148
+ else
149
+ result_clean "Skills directory not found or domain IOC empty; skipped exfiltration scan"
150
+ fi
151
+
152
+ # ============================================================
153
+ # CHECK 5 (origin 15): Known Malicious Publisher Detection
154
+ # ============================================================
155
+ header 5 "Checking installed skills against known malicious publishers..."
156
+
157
+ if [ -f "$IOC_DIR/malicious-publishers.txt" ] && [ -d "$SKILLS_DIR" ]; then
158
+ PUBLISHERS="$(grep -v '^#' "$IOC_DIR/malicious-publishers.txt" | grep -v '^$' | cut -d'|' -f1)"
159
+ PUB_FOUND=0
160
+ while IFS= read -r pub; do
161
+ [ -z "$pub" ] && continue
162
+ FOUND="$(grep -rlF --exclude-dir="$SELF_DIR_NAME" "$pub" "$SKILLS_DIR" 2>/dev/null || true)"
163
+ if [ -n "$FOUND" ]; then
164
+ if [ "$PUB_FOUND" -eq 0 ]; then
165
+ result_critical "Known malicious publisher references found"
166
+ PUB_FOUND=1
167
+ fi
168
+ log "Publisher '$pub' referenced in:"
169
+ log "$FOUND"
170
+ fi
171
+ done <<EOF
172
+ $PUBLISHERS
173
+ EOF
174
+ if [ "$PUB_FOUND" -eq 0 ]; then
175
+ result_clean "No known malicious publishers"
176
+ fi
177
+ else
178
+ result_clean "Publisher database not available or skills directory missing (skipped)"
179
+ fi
180
+
181
+ # ============================================================
182
+ # CHECK 6 (origin 23): Plugin/Extension Security
183
+ # ============================================================
184
+ header 6 "Auditing installed plugins and extensions..."
185
+
186
+ EXT_DIR="$OPENCLAW_DIR/extensions"
187
+ EXT_ISSUES=0
188
+
189
+ if [ -d "$EXT_DIR" ]; then
190
+ EXT_COUNT="$(find "$EXT_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')"
191
+ log "Installed extensions: $EXT_COUNT"
192
+
193
+ if [ "$EXT_COUNT" -gt 0 ]; then
194
+ while IFS= read -r ext; do
195
+ [ -z "$ext" ] && continue
196
+ EXT_NAME="$(basename "$ext")"
197
+
198
+ EXT_SUS="$(grep -rlE 'eval\(|exec\(|child_process|\.exec\(|net\.connect|http\.request|fetch\(' "$ext" 2>/dev/null | head -3 || true)"
199
+ if [ -n "$EXT_SUS" ]; then
200
+ result_warn "Extension '$EXT_NAME' has code-execution/network patterns"
201
+ log "$EXT_SUS"
202
+ EXT_ISSUES=$((EXT_ISSUES + 1))
203
+ fi
204
+
205
+ if [ -n "$DOMAIN_PATTERN" ]; then
206
+ EXT_MAL="$(grep -rlE "$DOMAIN_PATTERN" "$ext" 2>/dev/null || true)"
207
+ if [ -n "$EXT_MAL" ]; then
208
+ result_critical "Extension '$EXT_NAME' references known malicious domains"
209
+ log "$EXT_MAL"
210
+ EXT_ISSUES=$((EXT_ISSUES + 1))
211
+ fi
212
+ fi
213
+ done < <(find "$EXT_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
214
+ fi
215
+ fi
216
+
217
+ if [ "$EXT_ISSUES" -eq 0 ]; then
218
+ result_clean "No suspicious plugins/extensions"
219
+ fi
220
+
221
+ # ============================================================
222
+ # CHECK 7 (origin 30): VS Code Extension Trojan Detection
223
+ # ============================================================
224
+ header 7 "Checking for fake ClawdBot/OpenClaw VS Code extensions..."
225
+
226
+ VSCODE_ISSUES=0
227
+ VSCODE_EXT_DIR="$HOME/.vscode/extensions"
228
+ if [ -d "$VSCODE_EXT_DIR" ]; then
229
+ FAKE_EXT="$(find "$VSCODE_EXT_DIR" -maxdepth 1 -type d \( -iname "*clawdbot*" -o -iname "*moltbot*" -o -iname "*openclaw*" \) 2>/dev/null || true)"
230
+ if [ -n "$FAKE_EXT" ]; then
231
+ result_critical "Suspicious VS Code extension found (OpenClaw has no official VS Code extension)"
232
+ log "$FAKE_EXT"
233
+ VSCODE_ISSUES=$((VSCODE_ISSUES + 1))
234
+ fi
235
+ fi
236
+
237
+ VSCODE_INS_DIR="$HOME/.vscode-insiders/extensions"
238
+ if [ -d "$VSCODE_INS_DIR" ]; then
239
+ FAKE_INS="$(find "$VSCODE_INS_DIR" -maxdepth 1 -type d \( -iname "*clawdbot*" -o -iname "*moltbot*" -o -iname "*openclaw*" \) 2>/dev/null || true)"
240
+ if [ -n "$FAKE_INS" ]; then
241
+ result_critical "Suspicious VS Code Insiders extension found"
242
+ log "$FAKE_INS"
243
+ VSCODE_ISSUES=$((VSCODE_ISSUES + 1))
244
+ fi
245
+ fi
246
+
247
+ if [ "$VSCODE_ISSUES" -eq 0 ]; then
248
+ result_clean "No fake VS Code extensions"
249
+ fi
250
+
251
+ # ============================================================
252
+ # CHECK 8 (origin 16): Sensitive Environment Leakage
253
+ # ============================================================
254
+ header 8 "Scanning for sensitive environment/credential leakage..."
255
+
256
+ if [ -d "$SKILLS_DIR" ]; then
257
+ ENV_PATTERN='\.env|\.bashrc|\.zshrc|\.ssh/|id_rsa|id_ed25519|\.aws/credentials|\.kube/config|\.docker/config|keychain|login\.keychain|Cookies\.binarycookies|\.clawdbot/\.env|\.openclaw/openclaw\.json|auth-profiles\.json|\.git-credentials|\.netrc|moltbook.*token|moltbook.*api|MOLTBOOK_TOKEN|OPENAI_API_KEY|ANTHROPIC_API_KEY|sk-[a-zA-Z0-9]'
258
+ ENV_HITS="$(grep -rlinE --exclude-dir="$SELF_DIR_NAME" "cat.*(${ENV_PATTERN})|read.*(${ENV_PATTERN})|open.*(${ENV_PATTERN})|fs\.read.*(${ENV_PATTERN})|source.*(${ENV_PATTERN})" "$SKILLS_DIR" 2>/dev/null || true)"
259
+ API_KEY_HITS="$(grep -rlinE --exclude-dir="$SELF_DIR_NAME" "sk-[a-zA-Z0-9]{20,}|OPENAI_API_KEY\s*=\s*['\"][^$]|ANTHROPIC_API_KEY\s*=\s*['\"][^$]|moltbook.*token\s*=\s*['\"]" "$SKILLS_DIR" 2>/dev/null || true)"
260
+
261
+ if [ -n "$API_KEY_HITS" ]; then
262
+ result_critical "Hardcoded API keys or Moltbook tokens found in:"
263
+ log "$API_KEY_HITS"
264
+ fi
265
+
266
+ if [ -n "$ENV_HITS" ]; then
267
+ result_warn "Skills accessing sensitive env/credential files:"
268
+ log "$ENV_HITS"
269
+ fi
270
+
271
+ if [ -z "$API_KEY_HITS" ] && [ -z "$ENV_HITS" ]; then
272
+ result_clean "No sensitive environment leakage"
273
+ fi
274
+ else
275
+ result_clean "Skills directory not found; skipped sensitive env leakage scan"
276
+ fi
277
+
278
+ # ============================================================
279
+ # CHECK 9 (origin 10): Skill Poisoning / Memory File Modification
280
+ # ============================================================
281
+ header 9 "Checking skills for attempts to modify memory files..."
282
+
283
+ if [ -d "$SKILLS_DIR" ]; then
284
+ MEM_WRITE_HITS="$(
285
+ grep -rliE --exclude-dir="$SELF_DIR_NAME" 'SOUL\.md|MEMORY\.md|IDENTITY\.md' "$SKILLS_DIR" 2>/dev/null | while IFS= read -r f; do
286
+ if grep -qiE 'write.*SOUL|write.*MEMORY|write.*IDENTITY|modify.*SOUL|modify.*MEMORY|modify.*IDENTITY|echo.*>>.*SOUL|echo.*>>.*MEMORY|echo.*>>.*IDENTITY|cat.*>.*SOUL|cat.*>.*MEMORY|cat.*>.*IDENTITY|append.*SOUL|append.*MEMORY|append.*IDENTITY' "$f" 2>/dev/null; then
287
+ echo "$f"
288
+ fi
289
+ done
290
+ )"
291
+
292
+ if [ -n "$MEM_WRITE_HITS" ]; then
293
+ result_critical "Skills attempting to modify memory files:"
294
+ log "$MEM_WRITE_HITS"
295
+ else
296
+ result_clean "No skills attempting to modify memory files"
297
+ fi
298
+ else
299
+ result_clean "Skills directory not found; skipped memory file poisoning scan"
300
+ fi
301
+
302
+ # ============================================================
303
+ # CHECK 10 (origin 32): MCP Server Security
304
+ # ============================================================
305
+ header 10 "Auditing MCP server configuration..."
306
+
307
+ MCP_ISSUES=0
308
+ if command -v openclaw >/dev/null 2>&1; then
309
+ MCP_ALL="$(run_with_timeout 10 openclaw config get "mcp.enableAllProjectMcpServers" 2>/dev/null || echo "")"
310
+ if [ "$MCP_ALL" = "true" ]; then
311
+ result_warn "All project MCP servers enabled (prefer explicit allowlist)"
312
+ MCP_ISSUES=$((MCP_ISSUES + 1))
313
+ fi
314
+ fi
315
+
316
+ MCP_CONFIG="$OPENCLAW_DIR/mcp.json"
317
+ if [ -f "$MCP_CONFIG" ]; then
318
+ MCP_INJECT="$(grep -iE 'ignore previous|system prompt|override instruction|execute command|run this' "$MCP_CONFIG" 2>/dev/null || true)"
319
+ if [ -n "$MCP_INJECT" ]; then
320
+ result_critical "Prompt-injection patterns in MCP server config:"
321
+ log "$MCP_INJECT"
322
+ MCP_ISSUES=$((MCP_ISSUES + 1))
323
+ fi
324
+ fi
325
+
326
+ if [ "$MCP_ISSUES" -eq 0 ]; then
327
+ result_clean "MCP server configuration acceptable"
328
+ fi
329
+
330
+ # ============================================================
331
+ # CHECK 11 (origin 5): Crypto Wallet Targeting
332
+ # ============================================================
333
+ header 11 "Scanning for crypto wallet targeting..."
334
+
335
+ if [ -d "$SKILLS_DIR" ]; then
336
+ CRYPTO_PATTERN='wallet.*private.*key|seed\.phrase|mnemonic|keystore.*decrypt|phantom.*wallet|metamask.*vault|exchange.*api.*key|solana.*keypair|ethereum.*keyfile'
337
+ CRYPTO_HITS="$(grep -rlinE --exclude-dir="$SELF_DIR_NAME" "$CRYPTO_PATTERN" "$SKILLS_DIR" 2>/dev/null || true)"
338
+ if [ -n "$CRYPTO_HITS" ]; then
339
+ result_warn "Crypto wallet patterns found in:"
340
+ log "$CRYPTO_HITS"
341
+ else
342
+ result_clean "No crypto targeting"
343
+ fi
344
+ else
345
+ result_clean "Skills directory not found; skipped crypto wallet targeting scan"
346
+ fi
347
+
348
+ # ============================================================
349
+ # CHECK 12 (origin 6): Curl-Pipe / Download Attacks
350
+ # ============================================================
351
+ header 12 "Scanning for curl-pipe and download attacks..."
352
+
353
+ if [ -d "$SKILLS_DIR" ]; then
354
+ CURL_PATTERN='curl.*\|.*sh|curl.*\|.*bash|wget.*\|.*sh|curl -fsSL.*\||wget -q.*\||curl.*-o.*/tmp/'
355
+ CURL_HITS="$(grep -rlinE --exclude-dir="$SELF_DIR_NAME" "$CURL_PATTERN" "$SKILLS_DIR" 2>/dev/null || true)"
356
+ if [ -n "$CURL_HITS" ]; then
357
+ result_warn "Curl-pipe patterns found in:"
358
+ log "$CURL_HITS"
359
+ else
360
+ result_clean "No curl-pipe attacks"
361
+ fi
362
+ else
363
+ result_clean "Skills directory not found; skipped curl-pipe scan"
364
+ fi
365
+
366
+ # ============================================================
367
+ # CHECK 13 (origin 8): Skill Integrity Hashes
368
+ # ============================================================
369
+ header 13 "Computing skill integrity hashes..."
370
+
371
+ HASH_FILE="$LOG_DIR/skill-hashes.sha256"
372
+ HASH_FILE_PREV="$LOG_DIR/skill-hashes.sha256.prev"
373
+
374
+ if [ -f "$HASH_FILE" ]; then
375
+ cp "$HASH_FILE" "$HASH_FILE_PREV"
376
+ fi
377
+
378
+ if [ -d "$SKILLS_DIR" ]; then
379
+ find "$SKILLS_DIR" -name "SKILL.md" -type f -exec shasum -a 256 {} \; > "$HASH_FILE" 2>/dev/null || true
380
+
381
+ if [ -f "$HASH_FILE_PREV" ]; then
382
+ DIFF="$(diff "$HASH_FILE_PREV" "$HASH_FILE" 2>/dev/null || true)"
383
+ if [ -n "$DIFF" ]; then
384
+ result_warn "Skill files changed since last scan:"
385
+ log "$DIFF"
386
+ else
387
+ result_clean "No skill file modifications"
388
+ fi
389
+ else
390
+ log "INFO: Baseline hashes created (first scan)"
391
+ result_clean "Baseline hashes created"
392
+ fi
393
+ else
394
+ result_clean "Skills directory not found; skipped integrity hash scan"
395
+ fi
396
+
397
+ # ============================================================
398
+ # CHECK 14 (origin 9): SKILL.md Shell Injection Patterns
399
+ # ============================================================
400
+ header 14 "Scanning SKILL.md files for shell injection patterns..."
401
+
402
+ if [ -d "$SKILLS_DIR" ]; then
403
+ INJECTION_PATTERN='Prerequisites.*install|Prerequisites.*download|Prerequisites.*curl|Prerequisites.*wget|run this command.*terminal|paste.*terminal|copy.*terminal|base64 -d|base64 --decode|eval \$\(|exec \$\(|`curl|`wget|bypass.*safety.*guideline|execute.*without.*asking|ignore.*safety|override.*instruction|without.*user.*awareness'
404
+ INJECT_HITS=""
405
+
406
+ while IFS= read -r skillmd; do
407
+ [ -z "$skillmd" ] && continue
408
+ if grep -qiE "$INJECTION_PATTERN" "$skillmd" 2>/dev/null; then
409
+ if [ -z "$INJECT_HITS" ]; then
410
+ INJECT_HITS="$skillmd"
411
+ else
412
+ INJECT_HITS="${INJECT_HITS}
413
+ $skillmd"
414
+ fi
415
+ fi
416
+ done < <(find "$SKILLS_DIR" -name "SKILL.md" -type f -not -path "*/$SELF_DIR_NAME/*" 2>/dev/null)
417
+
418
+ if [ -n "$INJECT_HITS" ]; then
419
+ result_warn "SKILL.md files with suspicious install/injection instructions:"
420
+ log "$INJECT_HITS"
421
+ else
422
+ result_clean "No SKILL.md shell injection patterns"
423
+ fi
424
+ else
425
+ result_clean "Skills directory not found; skipped SKILL.md injection scan"
426
+ fi
427
+
428
+ # ============================================================
429
+ # CHECK 15 (origin 11): Base64 Obfuscation Detection
430
+ # ============================================================
431
+ header 15 "Scanning for base64-obfuscated payloads..."
432
+
433
+ if [ -d "$SKILLS_DIR" ]; then
434
+ B64_PATTERN='base64 -[dD]|base64 --decode|atob\(|Buffer\.from\(.*base64|echo.*\|.*base64.*\|.*bash|echo.*\|.*base64.*\|.*sh|python.*b64decode|import base64'
435
+ B64_HITS="$(grep -rlinE --exclude-dir="$SELF_DIR_NAME" "$B64_PATTERN" "$SKILLS_DIR" 2>/dev/null || true)"
436
+ if [ -n "$B64_HITS" ]; then
437
+ result_warn "Base64 decode patterns found in:"
438
+ log "$B64_HITS"
439
+ else
440
+ result_clean "No base64 obfuscation detected"
441
+ fi
442
+ else
443
+ result_clean "Skills directory not found; skipped base64 obfuscation scan"
444
+ fi
445
+
446
+ # ============================================================
447
+ # CHECK 16 (origin 12): External Binary Downloads
448
+ # ============================================================
449
+ header 16 "Scanning for external binary downloads..."
450
+
451
+ if [ -d "$SKILLS_DIR" ]; then
452
+ BIN_PATTERN='\.exe|\.dmg|\.pkg|\.msi|\.app\.zip|releases/download|github\.com/.*/releases|\.zip.*password|password.*\.zip|openclawcli\.zip|openclaw-agent|AuthTool.*download|download.*AuthTool'
453
+ BIN_HITS="$(grep -rlinE --exclude-dir="$SELF_DIR_NAME" "$BIN_PATTERN" "$SKILLS_DIR" 2>/dev/null || true)"
454
+ if [ -n "$BIN_HITS" ]; then
455
+ result_warn "External binary download references found in:"
456
+ log "$BIN_HITS"
457
+ else
458
+ result_clean "No external binary downloads"
459
+ fi
460
+ else
461
+ result_clean "Skills directory not found; skipped external binary download scan"
462
+ fi
463
+
464
+ # ============================================================
465
+ # CHECK 17 (origin 38): Skill Env Override Host Injection
466
+ # ============================================================
467
+ header 17 "Checking skill env override host injection (GHSA-82g8)..."
468
+
469
+ ENV_OVERRIDE_ISSUES=0
470
+
471
+ if [ -d "$SKILLS_DIR" ]; then
472
+ while IFS= read -r SKILL_DIR; do
473
+ [ -z "$SKILL_DIR" ] && continue
474
+ SKILL_NAME="$(basename "$SKILL_DIR")"
475
+
476
+ if [ "$SKILL_NAME" = "$SELF_DIR_NAME" ]; then
477
+ continue
478
+ fi
479
+
480
+ SKILL_MD="$SKILL_DIR/SKILL.md"
481
+ if [ -f "$SKILL_MD" ]; then
482
+ if grep -qiE '^\s*"?(HOST|PORT|OPENCLAW_|API_URL|BASE_URL|GATEWAY_URL|SERVER_URL)"?\s*:' "$SKILL_MD" 2>/dev/null; then
483
+ result_warn "Skill '$SKILL_NAME' declares HOST/PORT/URL env override (GHSA-82g8)"
484
+ ENV_OVERRIDE_ISSUES=$((ENV_OVERRIDE_ISSUES + 1))
485
+ fi
486
+ fi
487
+
488
+ for CFG in "$SKILL_DIR/package.json" "$SKILL_DIR/config.json" "$SKILL_DIR/.env"; do
489
+ if [ -f "$CFG" ]; then
490
+ if grep -qiE '(OPENCLAW_HOME|OPENCLAW_DIR|GATEWAY_URL|API_BASE|HOST=|PORT=)' "$CFG" 2>/dev/null; then
491
+ result_warn "Skill '$SKILL_NAME' overrides OpenClaw-related env in $(basename "$CFG") (GHSA-82g8)"
492
+ ENV_OVERRIDE_ISSUES=$((ENV_OVERRIDE_ISSUES + 1))
493
+ fi
494
+ fi
495
+ done
496
+ done < <(find "$SKILLS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
497
+ fi
498
+
499
+ if [ "$ENV_OVERRIDE_ISSUES" -eq 0 ]; then
500
+ result_clean "No suspicious skill env overrides found"
501
+ fi
502
+
503
+ # ============================================================
504
+ # CHECK 18 (new / SC-SKILL-003): Known malicious file hash IOC scan
505
+ # ============================================================
506
+ header 18 "Scanning skills for known malicious file hashes..."
507
+
508
+ HASH_IOC_ISSUES=0
509
+ HASH_IOCS_PRESENT=0
510
+
511
+ if [ -d "$SKILLS_DIR" ]; then
512
+ if [ -f "$IOC_DIR/malicious-hashes.txt" ]; then
513
+ HASH_IOCS_PRESENT=1
514
+ fi
515
+
516
+ if [ "$HASH_IOCS_PRESENT" -eq 1 ]; then
517
+ while IFS= read -r skill_dir; do
518
+ [ -z "$skill_dir" ] && continue
519
+ skill_name="$(basename "$skill_dir")"
520
+
521
+ if [ "$skill_name" = "$SELF_DIR_NAME" ]; then
522
+ continue
523
+ fi
524
+
525
+ while IFS= read -r f; do
526
+ [ -z "$f" ] && continue
527
+
528
+ file_hash="$(sha256_file "$f" || true)"
529
+ [ -n "$file_hash" ] || continue
530
+
531
+ campaign="$(lookup_malicious_hash_campaign "$file_hash" || true)"
532
+ if [ -n "$campaign" ]; then
533
+ result_critical "Known malicious file in skill '$skill_name' matches IOC campaign '$campaign'"
534
+ log " Evidence: $f"
535
+ log " SHA-256: $file_hash"
536
+ HASH_IOC_ISSUES=$((HASH_IOC_ISSUES + 1))
537
+ fi
538
+ done < <(find "$skill_dir" -type f 2>/dev/null)
539
+ done < <(find "$SKILLS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
540
+ else
541
+ log " malicious-hashes.txt not found under $IOC_DIR"
542
+ fi
543
+ else
544
+ result_clean "Skills directory not found; skipped malicious hash IOC scan"
545
+ fi
546
+
547
+ if [ -d "$SKILLS_DIR" ] && [ "$HASH_IOC_ISSUES" -eq 0 ]; then
548
+ if [ "$HASH_IOCS_PRESENT" -eq 1 ]; then
549
+ result_clean "No known malicious file hashes detected"
550
+ else
551
+ result_clean "Hash IOC database not found; malicious hash scan skipped"
552
+ fi
553
+ fi