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.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/bin/mohu-tui.js +73 -0
- package/bin/mohu-webui.js +67 -0
- package/dist/tui/tui.js +38733 -0
- package/dist/webui/index.html +1551 -0
- package/dist/webui/server.js +876 -0
- package/ioc/c2-ips.txt +25 -0
- package/ioc/file-hashes.txt +13 -0
- package/ioc/malicious-domains.txt +46 -0
- package/ioc/malicious-hashes.txt +5 -0
- package/ioc/malicious-publishers.txt +34 -0
- package/ioc/malicious-skill-patterns.txt +87 -0
- package/package.json +46 -0
- package/scripts/check/access_control.sh +183 -0
- package/scripts/check/credential_storage.sh +222 -0
- package/scripts/check/execution_sandbox.sh +502 -0
- package/scripts/check/memory_poisoning.sh +334 -0
- package/scripts/check/network_exposure.sh +479 -0
- package/scripts/check/resource_cost.sh +182 -0
- package/scripts/check/supply_chain.sh +553 -0
- package/scripts/repair/access_control/_common.sh +249 -0
- package/scripts/repair/access_control/check_1.sh +28 -0
- package/scripts/repair/access_control/check_2.sh +27 -0
- package/scripts/repair/access_control/check_3.sh +23 -0
- package/scripts/repair/access_control/check_4.sh +23 -0
- package/scripts/repair/access_control/check_5.sh +20 -0
- package/scripts/repair/credential_storage/_common.sh +277 -0
- package/scripts/repair/credential_storage/check_1.sh +47 -0
- package/scripts/repair/credential_storage/check_2.sh +35 -0
- package/scripts/repair/credential_storage/check_3.sh +53 -0
- package/scripts/repair/credential_storage/logs/security-scan.log +15 -0
- package/scripts/repair/execution_sandbox/_common.sh +302 -0
- package/scripts/repair/execution_sandbox/check_1.sh +67 -0
- package/scripts/repair/execution_sandbox/check_10.sh +23 -0
- package/scripts/repair/execution_sandbox/check_11.sh +34 -0
- package/scripts/repair/execution_sandbox/check_12.sh +38 -0
- package/scripts/repair/execution_sandbox/check_13.sh +29 -0
- package/scripts/repair/execution_sandbox/check_2.sh +46 -0
- package/scripts/repair/execution_sandbox/check_3.sh +37 -0
- package/scripts/repair/execution_sandbox/check_4.sh +23 -0
- package/scripts/repair/execution_sandbox/check_5.sh +28 -0
- package/scripts/repair/execution_sandbox/check_6.sh +17 -0
- package/scripts/repair/execution_sandbox/check_7.sh +17 -0
- package/scripts/repair/execution_sandbox/check_8.sh +17 -0
- package/scripts/repair/execution_sandbox/check_9.sh +17 -0
- package/scripts/repair/execution_sandbox/logs/security-scan.log +10 -0
- package/scripts/repair/memory_poisoning/_common.sh +336 -0
- package/scripts/repair/memory_poisoning/check_1.sh +51 -0
- package/scripts/repair/memory_poisoning/check_2.sh +26 -0
- package/scripts/repair/memory_poisoning/check_3.sh +24 -0
- package/scripts/repair/memory_poisoning/check_4.sh +27 -0
- package/scripts/repair/memory_poisoning/check_5.sh +20 -0
- package/scripts/repair/network_exposure/_common.sh +330 -0
- package/scripts/repair/network_exposure/check_1.sh +86 -0
- package/scripts/repair/network_exposure/check_10.sh +16 -0
- package/scripts/repair/network_exposure/check_11.sh +31 -0
- package/scripts/repair/network_exposure/check_12.sh +24 -0
- package/scripts/repair/network_exposure/check_2.sh +26 -0
- package/scripts/repair/network_exposure/check_3.sh +43 -0
- package/scripts/repair/network_exposure/check_4.sh +23 -0
- package/scripts/repair/network_exposure/check_5.sh +16 -0
- package/scripts/repair/network_exposure/check_6.sh +98 -0
- package/scripts/repair/network_exposure/check_7.sh +35 -0
- package/scripts/repair/network_exposure/check_8.sh +19 -0
- package/scripts/repair/network_exposure/check_9.sh +19 -0
- package/scripts/repair/resource_cost/_common.sh +303 -0
- package/scripts/repair/resource_cost/check_1.sh +16 -0
- package/scripts/repair/resource_cost/check_2.sh +16 -0
- package/scripts/repair/resource_cost/check_3.sh +23 -0
- package/scripts/repair/supply_chain/_common.sh +222 -0
- package/scripts/repair/supply_chain/check_1.sh +95 -0
- package/scripts/repair/supply_chain/check_10.sh +60 -0
- package/scripts/repair/supply_chain/check_11.sh +63 -0
- package/scripts/repair/supply_chain/check_12.sh +36 -0
- package/scripts/repair/supply_chain/check_13.sh +44 -0
- package/scripts/repair/supply_chain/check_14.sh +33 -0
- package/scripts/repair/supply_chain/check_15.sh +33 -0
- package/scripts/repair/supply_chain/check_16.sh +34 -0
- package/scripts/repair/supply_chain/check_17.sh +61 -0
- package/scripts/repair/supply_chain/check_18.sh +62 -0
- package/scripts/repair/supply_chain/check_2.sh +93 -0
- package/scripts/repair/supply_chain/check_3.sh +78 -0
- package/scripts/repair/supply_chain/check_4.sh +72 -0
- package/scripts/repair/supply_chain/check_5.sh +73 -0
- package/scripts/repair/supply_chain/check_6.sh +81 -0
- package/scripts/repair/supply_chain/check_7.sh +52 -0
- package/scripts/repair/supply_chain/check_8.sh +71 -0
- package/scripts/repair/supply_chain/check_9.sh +78 -0
- package/scripts/repair/supply_chain/logs/security-scan.log +77 -0
- package/scripts/scan.sh +228 -0
- 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
|