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,35 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 7 (origin 20): mDNS/Bonjour Exposure
|
|
3
|
+
# Usage: ./check_7.sh
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "$SCRIPT_DIR/_common.sh"
|
|
7
|
+
|
|
8
|
+
# Guidance
|
|
9
|
+
cat <<EOF
|
|
10
|
+
RECOMMENDED ACTIONS:
|
|
11
|
+
1. Disable mDNS broadcasting:
|
|
12
|
+
openclaw config set discovery.mdns.mode off
|
|
13
|
+
|
|
14
|
+
2. Or set to minimal (name only, no paths):
|
|
15
|
+
openclaw config set discovery.mdns.mode minimal
|
|
16
|
+
|
|
17
|
+
3. Verify mDNS is no longer broadcasting:
|
|
18
|
+
avahi-browse -a
|
|
19
|
+
# Should show no openclaw entries
|
|
20
|
+
|
|
21
|
+
4. Disable avahi-daemon entirely (if not needed):
|
|
22
|
+
sudo systemctl stop avahi-daemon
|
|
23
|
+
sudo systemctl disable avahi-daemon
|
|
24
|
+
|
|
25
|
+
5. Or restrict avahi to localhost only (/etc/avahi/avahi-daemon.conf):
|
|
26
|
+
[server]
|
|
27
|
+
allow-interfaces=lo
|
|
28
|
+
deny-interfaces=eth0,wlan0
|
|
29
|
+
|
|
30
|
+
Then restart:
|
|
31
|
+
sudo systemctl restart avahi-daemon
|
|
32
|
+
|
|
33
|
+
EOF
|
|
34
|
+
|
|
35
|
+
exit 0
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 8 (origin 34): SSRF Protection
|
|
3
|
+
# Usage: ./check_8.sh
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "$SCRIPT_DIR/_common.sh"
|
|
7
|
+
|
|
8
|
+
# Guidance
|
|
9
|
+
cat <<EOF
|
|
10
|
+
RECOMMENDED ACTIONS:
|
|
11
|
+
1. Update OpenClaw:
|
|
12
|
+
openclaw update
|
|
13
|
+
|
|
14
|
+
2. Audit cron webhook targets for internal endpoints:
|
|
15
|
+
openclaw config get cron
|
|
16
|
+
|
|
17
|
+
EOF
|
|
18
|
+
|
|
19
|
+
exit 0
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 9 (origin 36): ACP Permission Auto-Approval
|
|
3
|
+
# Usage: ./check_9.sh
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "$SCRIPT_DIR/_common.sh"
|
|
7
|
+
|
|
8
|
+
# Guidance
|
|
9
|
+
cat <<EOF
|
|
10
|
+
RECOMMENDED ACTIONS:
|
|
11
|
+
1. Update OpenClaw:
|
|
12
|
+
openclaw update
|
|
13
|
+
|
|
14
|
+
2. Disable ACP auto-approve until patched:
|
|
15
|
+
openclaw config set acp.autoApprove false
|
|
16
|
+
|
|
17
|
+
EOF
|
|
18
|
+
|
|
19
|
+
exit 0
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Exit codes: 0=SECURE, 1=WARNINGS, 2=COMPROMISED
|
|
3
|
+
|
|
4
|
+
set -uo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
# PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
8
|
+
# IOC_DIR="$PROJECT_DIR/ioc"
|
|
9
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
10
|
+
IOC_DIR="$PROJECT_DIR/ioc"
|
|
11
|
+
SELF_DIR_NAME="$(basename "$PROJECT_DIR")"
|
|
12
|
+
|
|
13
|
+
# Default OpenClaw paths
|
|
14
|
+
# OPENCLAW_DIR="${OPENCLAW_HOME:-$HOME/.openclaw}"
|
|
15
|
+
OPENCLAW_DIR="${OPENCLAW_HOME:-$HOME/test/openclaw-1}"
|
|
16
|
+
SKILLS_DIR="$OPENCLAW_DIR/workspace/skills"
|
|
17
|
+
WORKSPACE_DIR="$OPENCLAW_DIR/workspace"
|
|
18
|
+
# LOG_DIR="$OPENCLAW_DIR/logs"
|
|
19
|
+
LOG_DIR="logs"
|
|
20
|
+
LOG_FILE="$LOG_DIR/security-scan.log"
|
|
21
|
+
TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
22
|
+
|
|
23
|
+
export PATH="$HOME/.local/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
24
|
+
|
|
25
|
+
# Overall counters
|
|
26
|
+
CRITICAL=0
|
|
27
|
+
WARNINGS=0
|
|
28
|
+
CLEAN=0
|
|
29
|
+
|
|
30
|
+
# Per-category counters
|
|
31
|
+
CATEGORY_NAME=""
|
|
32
|
+
CATEGORY_TOTAL_CHECKS=0
|
|
33
|
+
CATEGORY_CRITICAL=0
|
|
34
|
+
CATEGORY_WARNINGS=0
|
|
35
|
+
CATEGORY_CLEAN=0
|
|
36
|
+
|
|
37
|
+
mkdir -p "$LOG_DIR"
|
|
38
|
+
|
|
39
|
+
log() {
|
|
40
|
+
echo "$1" | tee -a "$LOG_FILE"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
header() {
|
|
44
|
+
# Usage: header <new_index> <message>
|
|
45
|
+
log ""
|
|
46
|
+
log "[$1/$CATEGORY_TOTAL_CHECKS] $2"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
result_clean() {
|
|
50
|
+
log "CLEAN: $1"
|
|
51
|
+
CLEAN=$((CLEAN + 1))
|
|
52
|
+
CATEGORY_CLEAN=$((CATEGORY_CLEAN + 1))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
result_warn() {
|
|
56
|
+
log "WARNING: $1"
|
|
57
|
+
WARNINGS=$((WARNINGS + 1))
|
|
58
|
+
CATEGORY_WARNINGS=$((CATEGORY_WARNINGS + 1))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
result_critical() {
|
|
62
|
+
log "CRITICAL: $1"
|
|
63
|
+
CRITICAL=$((CRITICAL + 1))
|
|
64
|
+
CATEGORY_CRITICAL=$((CATEGORY_CRITICAL + 1))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
category_start() {
|
|
68
|
+
# Usage: category_start <name> <total_checks>
|
|
69
|
+
CATEGORY_NAME="$1"
|
|
70
|
+
CATEGORY_TOTAL_CHECKS="$2"
|
|
71
|
+
CATEGORY_CRITICAL=0
|
|
72
|
+
CATEGORY_WARNINGS=0
|
|
73
|
+
CATEGORY_CLEAN=0
|
|
74
|
+
|
|
75
|
+
log ""
|
|
76
|
+
log "----------------------------------------"
|
|
77
|
+
log "CATEGORY START: $CATEGORY_NAME ($CATEGORY_TOTAL_CHECKS checks)"
|
|
78
|
+
log "----------------------------------------"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
category_end() {
|
|
82
|
+
log ""
|
|
83
|
+
log "CATEGORY SUMMARY: $CATEGORY_NAME"
|
|
84
|
+
log " critical: $CATEGORY_CRITICAL"
|
|
85
|
+
log " warning : $CATEGORY_WARNINGS"
|
|
86
|
+
log " clean : $CATEGORY_CLEAN"
|
|
87
|
+
log "----------------------------------------"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Use timeout if available (macOS may only have gtimeout via coreutils)
|
|
91
|
+
TIMEOUT_BIN=""
|
|
92
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
93
|
+
TIMEOUT_BIN="timeout"
|
|
94
|
+
elif command -v gtimeout >/dev/null 2>&1; then
|
|
95
|
+
TIMEOUT_BIN="gtimeout"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
run_with_timeout() {
|
|
99
|
+
local secs="$1"
|
|
100
|
+
shift
|
|
101
|
+
|
|
102
|
+
if [ -n "$TIMEOUT_BIN" ]; then
|
|
103
|
+
"$TIMEOUT_BIN" "$secs" "$@"
|
|
104
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
105
|
+
python3 - "$secs" "$@" <<'PY'
|
|
106
|
+
import subprocess
|
|
107
|
+
import sys
|
|
108
|
+
|
|
109
|
+
def main():
|
|
110
|
+
try:
|
|
111
|
+
secs = float(sys.argv[1])
|
|
112
|
+
except Exception:
|
|
113
|
+
secs = 0
|
|
114
|
+
|
|
115
|
+
cmd = sys.argv[2:]
|
|
116
|
+
if not cmd:
|
|
117
|
+
sys.exit(1)
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
proc = subprocess.Popen(cmd)
|
|
121
|
+
try:
|
|
122
|
+
proc.wait(timeout=secs if secs > 0 else None)
|
|
123
|
+
except subprocess.TimeoutExpired:
|
|
124
|
+
proc.kill()
|
|
125
|
+
proc.wait()
|
|
126
|
+
sys.exit(124)
|
|
127
|
+
sys.exit(proc.returncode if proc.returncode is not None else 0)
|
|
128
|
+
except FileNotFoundError:
|
|
129
|
+
sys.exit(127)
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
main()
|
|
133
|
+
PY
|
|
134
|
+
else
|
|
135
|
+
"$@"
|
|
136
|
+
fi
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Load IOC data
|
|
140
|
+
load_ips() {
|
|
141
|
+
if [ -f "$IOC_DIR/c2-ips.txt" ]; then
|
|
142
|
+
grep -v '^#' "$IOC_DIR/c2-ips.txt" | grep -v '^$' | cut -d'|' -f1
|
|
143
|
+
else
|
|
144
|
+
echo "91.92.242"
|
|
145
|
+
fi
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
load_domains() {
|
|
149
|
+
if [ -f "$IOC_DIR/malicious-domains.txt" ]; then
|
|
150
|
+
grep -v '^#' "$IOC_DIR/malicious-domains.txt" | grep -v '^$' | cut -d'|' -f1
|
|
151
|
+
else
|
|
152
|
+
echo "webhook.site"
|
|
153
|
+
fi
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
sha256_file() {
|
|
157
|
+
# Cross-platform SHA-256 for a file
|
|
158
|
+
local target="$1"
|
|
159
|
+
[ -f "$target" ] || return 1
|
|
160
|
+
|
|
161
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
162
|
+
sha256sum "$target" 2>/dev/null | awk '{print tolower($1)}'
|
|
163
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
164
|
+
shasum -a 256 "$target" 2>/dev/null | awk '{print tolower($1)}'
|
|
165
|
+
elif command -v openssl >/dev/null 2>&1; then
|
|
166
|
+
openssl dgst -sha256 "$target" 2>/dev/null | sed -E 's/^.*= //' | tr '[:upper:]' '[:lower:]'
|
|
167
|
+
else
|
|
168
|
+
return 1
|
|
169
|
+
fi
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
load_hash_iocs() {
|
|
173
|
+
# Expected format: <sha256>|<campaign>
|
|
174
|
+
# Similar to c2-ips.txt storage style
|
|
175
|
+
if [ -f "$IOC_DIR/malicious-hashes.txt" ]; then
|
|
176
|
+
grep -v '^#' "$IOC_DIR/malicious-hashes.txt" | grep -v '^$' || true
|
|
177
|
+
fi
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
lookup_malicious_hash_campaign() {
|
|
181
|
+
local needle
|
|
182
|
+
needle="$(printf "%s" "${1:-}" | tr '[:upper:]' '[:lower:]')"
|
|
183
|
+
[ -n "$needle" ] || return 1
|
|
184
|
+
|
|
185
|
+
while IFS='|' read -r hash_val campaign rest; do
|
|
186
|
+
hash_val="$(printf "%s" "$hash_val" | tr '[:upper:]' '[:lower:]')"
|
|
187
|
+
if [ "$hash_val" = "$needle" ]; then
|
|
188
|
+
printf "%s\n" "${campaign:-unknown}"
|
|
189
|
+
return 0
|
|
190
|
+
fi
|
|
191
|
+
done < <(load_hash_iocs) # ← 进程替换,不产生子shell
|
|
192
|
+
|
|
193
|
+
return 1
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
extract_github_account_age_days() {
|
|
197
|
+
# Best-effort local metadata extraction only.
|
|
198
|
+
# We cannot reliably infer GitHub account age from filesystem alone unless
|
|
199
|
+
# the skill metadata explicitly records it.
|
|
200
|
+
local skill_dir="$1"
|
|
201
|
+
local val=""
|
|
202
|
+
|
|
203
|
+
for meta in "$skill_dir/package.json" "$skill_dir/config.json" "$skill_dir/SKILL.md"; do
|
|
204
|
+
[ -f "$meta" ] || continue
|
|
205
|
+
|
|
206
|
+
# JSON-ish keys
|
|
207
|
+
val="$(grep -iEo '"(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)"[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
|
|
208
|
+
if [ -n "$val" ]; then
|
|
209
|
+
printf "%s\n" "$val"
|
|
210
|
+
return 0
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# YAML / markdown frontmatter-ish keys
|
|
214
|
+
val="$(grep -iE '^(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
|
|
215
|
+
if [ -n "$val" ]; then
|
|
216
|
+
printf "%s\n" "$val"
|
|
217
|
+
return 0
|
|
218
|
+
fi
|
|
219
|
+
done
|
|
220
|
+
|
|
221
|
+
return 1
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
OPENCLAW_PRESENT=false
|
|
226
|
+
if command -v openclaw >/dev/null 2>&1; then
|
|
227
|
+
OPENCLAW_PRESENT=true
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
OC_VERSION="unknown"
|
|
231
|
+
PARSED_OC_VERSION=""
|
|
232
|
+
OC_MAJOR=""
|
|
233
|
+
OC_MINOR=""
|
|
234
|
+
OC_PATCH=""
|
|
235
|
+
|
|
236
|
+
if [ "$OPENCLAW_PRESENT" = true ]; then
|
|
237
|
+
OC_VERSION="$(run_with_timeout 5 openclaw --version 2>/dev/null || echo "unknown")"
|
|
238
|
+
PARSED_OC_VERSION="$(echo "$OC_VERSION" | grep -oE '[0-9]{4}\.[0-9]+\.[0-9]+' | head -1 || true)"
|
|
239
|
+
|
|
240
|
+
if [ -n "$PARSED_OC_VERSION" ]; then
|
|
241
|
+
OC_MAJOR="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f1)"
|
|
242
|
+
OC_MINOR="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f2)"
|
|
243
|
+
OC_PATCH="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f3)"
|
|
244
|
+
fi
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
version_ge() {
|
|
248
|
+
|
|
249
|
+
local t_major="$1"
|
|
250
|
+
local t_minor="$2"
|
|
251
|
+
local t_patch="$3"
|
|
252
|
+
|
|
253
|
+
if [ -z "$PARSED_OC_VERSION" ]; then
|
|
254
|
+
return 1
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
if [ "$OC_MAJOR" -gt "$t_major" ] 2>/dev/null; then
|
|
258
|
+
return 0
|
|
259
|
+
fi
|
|
260
|
+
if [ "$OC_MAJOR" -lt "$t_major" ] 2>/dev/null; then
|
|
261
|
+
return 1
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
if [ "$OC_MINOR" -gt "$t_minor" ] 2>/dev/null; then
|
|
265
|
+
return 0
|
|
266
|
+
fi
|
|
267
|
+
if [ "$OC_MINOR" -lt "$t_minor" ] 2>/dev/null; then
|
|
268
|
+
return 1
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
if [ "$OC_PATCH" -ge "$t_patch" ] 2>/dev/null; then
|
|
272
|
+
return 0
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
return 1
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
version_lt() {
|
|
279
|
+
|
|
280
|
+
if [ -z "$PARSED_OC_VERSION" ]; then
|
|
281
|
+
return 1
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
if version_ge "$1" "$2" "$3"; then
|
|
285
|
+
return 1
|
|
286
|
+
fi
|
|
287
|
+
|
|
288
|
+
return 0
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
# ------------------------------------------------------------
|
|
292
|
+
# Cron frequency check
|
|
293
|
+
# ------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
has_high_freq_cron() {
|
|
296
|
+
|
|
297
|
+
grep -Eq '^[[:space:]]*(\*[[:space:]]+\*[[:space:]]+\*[[:space:]]+\*[[:space:]]+\*|\*/[1-4][[:space:]]+\*[[:space:]]+\*[[:space:]]+\*[[:space:]]+\*)' "$1" 2>/dev/null
|
|
298
|
+
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
TOTAL_TOKENS=0
|
|
302
|
+
TOTAL_COST=0
|
|
303
|
+
HOURLY_COST=0
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 1: Webhook DoS
|
|
3
|
+
# Usage: ./check_1.sh
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "$SCRIPT_DIR/_common.sh"
|
|
7
|
+
|
|
8
|
+
# Guidance
|
|
9
|
+
cat <<EOF
|
|
10
|
+
RECOMMENDED ACTIONS:
|
|
11
|
+
1. Update OpenClaw:
|
|
12
|
+
openclaw update
|
|
13
|
+
|
|
14
|
+
EOF
|
|
15
|
+
|
|
16
|
+
exit 0
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 2: fetchWithGuard DoS
|
|
3
|
+
# Usage: ./check_2.sh
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "$SCRIPT_DIR/_common.sh"
|
|
7
|
+
|
|
8
|
+
# Guidance
|
|
9
|
+
cat <<EOF
|
|
10
|
+
RECOMMENDED ACTIONS:
|
|
11
|
+
1. Update OpenClaw:
|
|
12
|
+
openclaw update
|
|
13
|
+
|
|
14
|
+
EOF
|
|
15
|
+
|
|
16
|
+
exit 0
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 3: High frequency cron
|
|
3
|
+
# Usage: ./check_3.sh
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "$SCRIPT_DIR/_common.sh"
|
|
7
|
+
|
|
8
|
+
# Guidance
|
|
9
|
+
cat <<EOF
|
|
10
|
+
RECOMMENDED ACTIONS:
|
|
11
|
+
1. Review current crontab schedules:
|
|
12
|
+
crontab -l | grep -iE "openclaw|clawdbot|moltbot"
|
|
13
|
+
|
|
14
|
+
2. Edit and reduce trigger frequency (no more than once per 5 minutes):
|
|
15
|
+
crontab -e
|
|
16
|
+
# Change */1 to */5 or higher
|
|
17
|
+
|
|
18
|
+
3. Consider switching to event-driven triggers instead of fixed schedules:
|
|
19
|
+
openclaw config set scheduler.mode event-driven
|
|
20
|
+
|
|
21
|
+
EOF
|
|
22
|
+
|
|
23
|
+
exit 0
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Exit codes: 0=SECURE, 1=WARNINGS, 2=COMPROMISED
|
|
3
|
+
|
|
4
|
+
set -uo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
# PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
8
|
+
# IOC_DIR="$PROJECT_DIR/ioc"
|
|
9
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
10
|
+
IOC_DIR="$PROJECT_DIR/ioc"
|
|
11
|
+
SELF_DIR_NAME="$(basename "$PROJECT_DIR")"
|
|
12
|
+
|
|
13
|
+
# Default OpenClaw paths
|
|
14
|
+
# OPENCLAW_DIR="${OPENCLAW_HOME:-$HOME/.openclaw}"
|
|
15
|
+
OPENCLAW_DIR="${OPENCLAW_HOME:-$HOME/test/openclaw-1}"
|
|
16
|
+
SKILLS_DIR="$OPENCLAW_DIR/workspace/skills"
|
|
17
|
+
WORKSPACE_DIR="$OPENCLAW_DIR/workspace"
|
|
18
|
+
# LOG_DIR="$OPENCLAW_DIR/logs"
|
|
19
|
+
LOG_DIR="logs"
|
|
20
|
+
LOG_FILE="$LOG_DIR/security-scan.log"
|
|
21
|
+
TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
22
|
+
|
|
23
|
+
export PATH="$HOME/.local/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
24
|
+
|
|
25
|
+
# Overall counters
|
|
26
|
+
CRITICAL=0
|
|
27
|
+
WARNINGS=0
|
|
28
|
+
CLEAN=0
|
|
29
|
+
|
|
30
|
+
# Per-category counters
|
|
31
|
+
CATEGORY_NAME=""
|
|
32
|
+
CATEGORY_TOTAL_CHECKS=0
|
|
33
|
+
CATEGORY_CRITICAL=0
|
|
34
|
+
CATEGORY_WARNINGS=0
|
|
35
|
+
CATEGORY_CLEAN=0
|
|
36
|
+
|
|
37
|
+
mkdir -p "$LOG_DIR"
|
|
38
|
+
|
|
39
|
+
log() {
|
|
40
|
+
echo "$1" | tee -a "$LOG_FILE"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
header() {
|
|
44
|
+
# Usage: header <new_index> <message>
|
|
45
|
+
log ""
|
|
46
|
+
log "[$1/$CATEGORY_TOTAL_CHECKS] $2"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
result_clean() {
|
|
50
|
+
log "CLEAN: $1"
|
|
51
|
+
CLEAN=$((CLEAN + 1))
|
|
52
|
+
CATEGORY_CLEAN=$((CATEGORY_CLEAN + 1))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
result_warn() {
|
|
56
|
+
log "WARNING: $1"
|
|
57
|
+
WARNINGS=$((WARNINGS + 1))
|
|
58
|
+
CATEGORY_WARNINGS=$((CATEGORY_WARNINGS + 1))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
result_critical() {
|
|
62
|
+
log "CRITICAL: $1"
|
|
63
|
+
CRITICAL=$((CRITICAL + 1))
|
|
64
|
+
CATEGORY_CRITICAL=$((CATEGORY_CRITICAL + 1))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
category_start() {
|
|
68
|
+
# Usage: category_start <name> <total_checks>
|
|
69
|
+
CATEGORY_NAME="$1"
|
|
70
|
+
CATEGORY_TOTAL_CHECKS="$2"
|
|
71
|
+
CATEGORY_CRITICAL=0
|
|
72
|
+
CATEGORY_WARNINGS=0
|
|
73
|
+
CATEGORY_CLEAN=0
|
|
74
|
+
|
|
75
|
+
log ""
|
|
76
|
+
log "----------------------------------------"
|
|
77
|
+
log "CATEGORY START: $CATEGORY_NAME ($CATEGORY_TOTAL_CHECKS checks)"
|
|
78
|
+
log "----------------------------------------"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
category_end() {
|
|
82
|
+
log ""
|
|
83
|
+
log "CATEGORY SUMMARY: $CATEGORY_NAME"
|
|
84
|
+
log " critical: $CATEGORY_CRITICAL"
|
|
85
|
+
log " warning : $CATEGORY_WARNINGS"
|
|
86
|
+
log " clean : $CATEGORY_CLEAN"
|
|
87
|
+
log "----------------------------------------"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Use timeout if available (macOS may only have gtimeout via coreutils)
|
|
91
|
+
TIMEOUT_BIN=""
|
|
92
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
93
|
+
TIMEOUT_BIN="timeout"
|
|
94
|
+
elif command -v gtimeout >/dev/null 2>&1; then
|
|
95
|
+
TIMEOUT_BIN="gtimeout"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
run_with_timeout() {
|
|
99
|
+
local secs="$1"
|
|
100
|
+
shift
|
|
101
|
+
|
|
102
|
+
if [ -n "$TIMEOUT_BIN" ]; then
|
|
103
|
+
"$TIMEOUT_BIN" "$secs" "$@"
|
|
104
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
105
|
+
python3 - "$secs" "$@" <<'PY'
|
|
106
|
+
import subprocess
|
|
107
|
+
import sys
|
|
108
|
+
|
|
109
|
+
def main():
|
|
110
|
+
try:
|
|
111
|
+
secs = float(sys.argv[1])
|
|
112
|
+
except Exception:
|
|
113
|
+
secs = 0
|
|
114
|
+
|
|
115
|
+
cmd = sys.argv[2:]
|
|
116
|
+
if not cmd:
|
|
117
|
+
sys.exit(1)
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
proc = subprocess.Popen(cmd)
|
|
121
|
+
try:
|
|
122
|
+
proc.wait(timeout=secs if secs > 0 else None)
|
|
123
|
+
except subprocess.TimeoutExpired:
|
|
124
|
+
proc.kill()
|
|
125
|
+
proc.wait()
|
|
126
|
+
sys.exit(124)
|
|
127
|
+
sys.exit(proc.returncode if proc.returncode is not None else 0)
|
|
128
|
+
except FileNotFoundError:
|
|
129
|
+
sys.exit(127)
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
main()
|
|
133
|
+
PY
|
|
134
|
+
else
|
|
135
|
+
"$@"
|
|
136
|
+
fi
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Load IOC data
|
|
140
|
+
load_ips() {
|
|
141
|
+
if [ -f "$IOC_DIR/c2-ips.txt" ]; then
|
|
142
|
+
grep -v '^#' "$IOC_DIR/c2-ips.txt" | grep -v '^$' | cut -d'|' -f1
|
|
143
|
+
else
|
|
144
|
+
echo "91.92.242"
|
|
145
|
+
fi
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
load_domains() {
|
|
149
|
+
if [ -f "$IOC_DIR/malicious-domains.txt" ]; then
|
|
150
|
+
grep -v '^#' "$IOC_DIR/malicious-domains.txt" | grep -v '^$' | cut -d'|' -f1
|
|
151
|
+
else
|
|
152
|
+
echo "webhook.site"
|
|
153
|
+
fi
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
sha256_file() {
|
|
157
|
+
# Cross-platform SHA-256 for a file
|
|
158
|
+
local target="$1"
|
|
159
|
+
[ -f "$target" ] || return 1
|
|
160
|
+
|
|
161
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
162
|
+
sha256sum "$target" 2>/dev/null | awk '{print tolower($1)}'
|
|
163
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
164
|
+
shasum -a 256 "$target" 2>/dev/null | awk '{print tolower($1)}'
|
|
165
|
+
elif command -v openssl >/dev/null 2>&1; then
|
|
166
|
+
openssl dgst -sha256 "$target" 2>/dev/null | sed -E 's/^.*= //' | tr '[:upper:]' '[:lower:]'
|
|
167
|
+
else
|
|
168
|
+
return 1
|
|
169
|
+
fi
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
load_hash_iocs() {
|
|
173
|
+
# Expected format: <sha256>|<campaign>
|
|
174
|
+
# Similar to c2-ips.txt storage style
|
|
175
|
+
if [ -f "$IOC_DIR/malicious-hashes.txt" ]; then
|
|
176
|
+
grep -v '^#' "$IOC_DIR/malicious-hashes.txt" | grep -v '^$' || true
|
|
177
|
+
fi
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
lookup_malicious_hash_campaign() {
|
|
181
|
+
local needle
|
|
182
|
+
needle="$(printf "%s" "${1:-}" | tr '[:upper:]' '[:lower:]')"
|
|
183
|
+
[ -n "$needle" ] || return 1
|
|
184
|
+
|
|
185
|
+
while IFS='|' read -r hash_val campaign rest; do
|
|
186
|
+
hash_val="$(printf "%s" "$hash_val" | tr '[:upper:]' '[:lower:]')"
|
|
187
|
+
if [ "$hash_val" = "$needle" ]; then
|
|
188
|
+
printf "%s\n" "${campaign:-unknown}"
|
|
189
|
+
return 0
|
|
190
|
+
fi
|
|
191
|
+
done < <(load_hash_iocs) # ← 进程替换,不产生子shell
|
|
192
|
+
|
|
193
|
+
return 1
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
extract_github_account_age_days() {
|
|
197
|
+
# Best-effort local metadata extraction only.
|
|
198
|
+
# We cannot reliably infer GitHub account age from filesystem alone unless
|
|
199
|
+
# the skill metadata explicitly records it.
|
|
200
|
+
local skill_dir="$1"
|
|
201
|
+
local val=""
|
|
202
|
+
|
|
203
|
+
for meta in "$skill_dir/package.json" "$skill_dir/config.json" "$skill_dir/SKILL.md"; do
|
|
204
|
+
[ -f "$meta" ] || continue
|
|
205
|
+
|
|
206
|
+
# JSON-ish keys
|
|
207
|
+
val="$(grep -iEo '"(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)"[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
|
|
208
|
+
if [ -n "$val" ]; then
|
|
209
|
+
printf "%s\n" "$val"
|
|
210
|
+
return 0
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# YAML / markdown frontmatter-ish keys
|
|
214
|
+
val="$(grep -iE '^(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
|
|
215
|
+
if [ -n "$val" ]; then
|
|
216
|
+
printf "%s\n" "$val"
|
|
217
|
+
return 0
|
|
218
|
+
fi
|
|
219
|
+
done
|
|
220
|
+
|
|
221
|
+
return 1
|
|
222
|
+
}
|