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,502 @@
1
+ # shellcheck shell=bash
2
+
3
+ # ------------------------------------------------------------
4
+ # Shared helpers
5
+ # ------------------------------------------------------------
6
+
7
+ OPENCLAW_PRESENT=false
8
+ if command -v openclaw >/dev/null 2>&1; then
9
+ OPENCLAW_PRESENT=true
10
+ fi
11
+
12
+ OC_VERSION="unknown"
13
+ PARSED_OC_VERSION=""
14
+ OC_MAJOR=""
15
+ OC_MINOR=""
16
+ OC_PATCH=""
17
+
18
+ if [ "$OPENCLAW_PRESENT" = true ]; then
19
+ OC_VERSION="$(run_with_timeout 5 openclaw --version 2>/dev/null || echo "unknown")"
20
+ PARSED_OC_VERSION="$(echo "$OC_VERSION" | grep -oE '[0-9]{4}\.[0-9]+\.[0-9]+' | head -1 || true)"
21
+ if [ -n "$PARSED_OC_VERSION" ]; then
22
+ OC_MAJOR="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f1)"
23
+ OC_MINOR="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f2)"
24
+ OC_PATCH="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f3)"
25
+ fi
26
+ fi
27
+
28
+ get_oc_config() {
29
+ # Usage: get_oc_config <key> <default>
30
+ local key="$1"
31
+ local default_val="${2:-}"
32
+ if [ "$OPENCLAW_PRESENT" = true ]; then
33
+ run_with_timeout 10 openclaw config get "$key" 2>/dev/null || echo "$default_val"
34
+ else
35
+ echo "$default_val"
36
+ fi
37
+ }
38
+
39
+ version_ge() {
40
+ # Usage: version_ge <major> <minor> <patch>
41
+ local t_major="$1"
42
+ local t_minor="$2"
43
+ local t_patch="$3"
44
+
45
+ if [ -z "$PARSED_OC_VERSION" ]; then
46
+ return 1
47
+ fi
48
+
49
+ if [ "$OC_MAJOR" -gt "$t_major" ] 2>/dev/null; then
50
+ return 0
51
+ fi
52
+ if [ "$OC_MAJOR" -lt "$t_major" ] 2>/dev/null; then
53
+ return 1
54
+ fi
55
+
56
+ if [ "$OC_MINOR" -gt "$t_minor" ] 2>/dev/null; then
57
+ return 0
58
+ fi
59
+ if [ "$OC_MINOR" -lt "$t_minor" ] 2>/dev/null; then
60
+ return 1
61
+ fi
62
+
63
+ if [ "$OC_PATCH" -ge "$t_patch" ] 2>/dev/null; then
64
+ return 0
65
+ fi
66
+
67
+ return 1
68
+ }
69
+
70
+ version_lt() {
71
+ # Usage: version_lt <major> <minor> <patch>
72
+ if [ -z "$PARSED_OC_VERSION" ]; then
73
+ return 1
74
+ fi
75
+ if version_ge "$1" "$2" "$3"; then
76
+ return 1
77
+ fi
78
+ return 0
79
+ }
80
+
81
+ get_perm() {
82
+ local target="$1"
83
+
84
+ [ -e "$target" ] || {
85
+ echo "unknown"
86
+ return
87
+ }
88
+
89
+ case "$(uname -s)" in
90
+ Darwin|FreeBSD|OpenBSD|NetBSD)
91
+ stat -f '%Lp' "$target" 2>/dev/null || echo "unknown"
92
+ ;;
93
+ Linux)
94
+ stat -c '%a' "$target" 2>/dev/null || echo "unknown"
95
+ ;;
96
+ *)
97
+ stat -c '%a' "$target" 2>/dev/null || stat -f '%Lp' "$target" 2>/dev/null || echo "unknown"
98
+ ;;
99
+ esac
100
+ }
101
+
102
+ # ============================================================
103
+ # CHECK 1 (origin 18): Tool Policy / Elevated Tools Audit
104
+ # ============================================================
105
+ header 1 "Auditing tool policies and elevated access..."
106
+
107
+ TOOL_ISSUES=0
108
+
109
+ if [ "$OPENCLAW_PRESENT" = true ]; then
110
+ ELEVATED="$(get_oc_config "tools.elevated.enabled" "")"
111
+ if [ "$ELEVATED" = "true" ]; then
112
+ ELEVATED_ALLOW="$(get_oc_config "tools.elevated.allowFrom" "")"
113
+ if echo "$ELEVATED_ALLOW" | grep -qE '"\*"|\*' 2>/dev/null; then
114
+ result_critical "Elevated tools enabled with wildcard allowFrom"
115
+ TOOL_ISSUES=$((TOOL_ISSUES + 1))
116
+ else
117
+ log " INFO: Elevated tools enabled with restricted allowFrom"
118
+ fi
119
+ fi
120
+
121
+ DENY_LIST="$(get_oc_config "tools.deny" "")"
122
+ if [ -z "$DENY_LIST" ] || [ "$DENY_LIST" = "[]" ] || [ "$DENY_LIST" = "null" ]; then
123
+ result_warn "No tools in deny list (consider blocking high-risk tools such as exec/process/browser)"
124
+ TOOL_ISSUES=$((TOOL_ISSUES + 1))
125
+ fi
126
+ fi
127
+
128
+ if [ "$TOOL_ISSUES" -eq 0 ]; then
129
+ result_clean "Tool policies acceptable"
130
+ fi
131
+
132
+ # ============================================================
133
+ # CHECK 2 (origin 26): Exec-Approvals Configuration
134
+ # ============================================================
135
+ header 2 "Auditing exec-approvals configuration..."
136
+
137
+ EXEC_ISSUES=0
138
+ EXEC_FILE="$OPENCLAW_DIR/exec-approvals.json"
139
+
140
+ if [ -f "$EXEC_FILE" ]; then
141
+ UNSAFE_EXEC="$(grep -iE '"security"\s*:\s*"allow"|"ask"\s*:\s*"off"|"allowlist"\s*:\s*\[\s*\]' "$EXEC_FILE" 2>/dev/null || true)"
142
+ if [ -n "$UNSAFE_EXEC" ]; then
143
+ result_critical "Exec-approvals has unsafe configuration"
144
+ log "$UNSAFE_EXEC"
145
+ EXEC_ISSUES=$((EXEC_ISSUES + 1))
146
+ fi
147
+
148
+ EXEC_PERMS="$(get_perm "$EXEC_FILE")"
149
+ if [ "$EXEC_PERMS" != "600" ] && [ "$EXEC_PERMS" != "unknown" ]; then
150
+ result_warn "exec-approvals.json has permissions $EXEC_PERMS (recommended: 600)"
151
+ EXEC_ISSUES=$((EXEC_ISSUES + 1))
152
+ fi
153
+ fi
154
+
155
+ if [ "$EXEC_ISSUES" -eq 0 ]; then
156
+ result_clean "Exec-approvals configuration acceptable"
157
+ fi
158
+
159
+ # ============================================================
160
+ # CHECK 3 (origin 27 + EXEC-005/006/007): Docker Container Security
161
+ # ============================================================
162
+ header 3 "Auditing Docker container security..."
163
+
164
+ DOCKER_ISSUES=0
165
+
166
+ if command -v docker >/dev/null 2>&1; then
167
+ OC_CONTAINERS="$(docker ps --format '{{.Names}} {{.Image}}' 2>/dev/null | grep -iE 'openclaw|clawdbot|moltbot' || true)"
168
+ if [ -n "$OC_CONTAINERS" ]; then
169
+ while IFS= read -r container_line; do
170
+ [ -z "$container_line" ] && continue
171
+ CNAME="$(echo "$container_line" | awk '{print $1}')"
172
+
173
+ CUSER="$(docker inspect --format '{{.Config.User}}' "$CNAME" 2>/dev/null || echo "")"
174
+ if [ -z "$CUSER" ] || [ "$CUSER" = "root" ] || [ "$CUSER" = "0" ]; then
175
+ result_warn "Container '$CNAME' running as root"
176
+ DOCKER_ISSUES=$((DOCKER_ISSUES + 1))
177
+ fi
178
+
179
+ DSOCK="$(docker inspect --format '{{range .Mounts}}{{.Source}} {{end}}' "$CNAME" 2>/dev/null | grep 'docker.sock' || true)"
180
+ if [ -n "$DSOCK" ]; then
181
+ result_critical "Container '$CNAME' has Docker socket mounted (container escape risk)"
182
+ DOCKER_ISSUES=$((DOCKER_ISSUES + 1))
183
+ fi
184
+
185
+ PRIV="$(docker inspect --format '{{.HostConfig.Privileged}}' "$CNAME" 2>/dev/null || echo "")"
186
+ if [ "$PRIV" = "true" ]; then
187
+ result_critical "Container '$CNAME' is running in privileged mode"
188
+ DOCKER_ISSUES=$((DOCKER_ISSUES + 1))
189
+ fi
190
+
191
+ # EXEC-005: cap_drop should include ALL
192
+ CAP_DROP_ALL="$(docker inspect --format '{{range .HostConfig.CapDrop}}{{println .}}{{end}}' "$CNAME" 2>/dev/null || true)"
193
+ if ! echo "$CAP_DROP_ALL" | grep -qx 'ALL'; then
194
+ result_warn "Container '$CNAME' retains Linux capabilities (cap_drop does not include ALL)"
195
+ DOCKER_ISSUES=$((DOCKER_ISSUES + 1))
196
+ fi
197
+
198
+ # EXEC-006: no-new-privileges:true should be enabled
199
+ SECURITY_OPTS="$(docker inspect --format '{{range .HostConfig.SecurityOpt}}{{println .}}{{end}}' "$CNAME" 2>/dev/null || true)"
200
+ if ! echo "$SECURITY_OPTS" | grep -qx 'no-new-privileges:true'; then
201
+ result_warn "Container '$CNAME' allows privilege escalation (security_opt missing no-new-privileges:true)"
202
+ DOCKER_ISSUES=$((DOCKER_ISSUES + 1))
203
+ fi
204
+
205
+ # EXEC-007: network_mode should not be host
206
+ NETWORK_MODE="$(docker inspect --format '{{.HostConfig.NetworkMode}}' "$CNAME" 2>/dev/null || echo "")"
207
+ if [ "$NETWORK_MODE" = "host" ]; then
208
+ result_critical "Container '$CNAME' uses host network mode (bypasses network isolation)"
209
+ DOCKER_ISSUES=$((DOCKER_ISSUES + 1))
210
+ fi
211
+ done <<EOF
212
+ $OC_CONTAINERS
213
+ EOF
214
+ else
215
+ log " No OpenClaw-related Docker containers detected"
216
+ fi
217
+ fi
218
+
219
+ if [ "$DOCKER_ISSUES" -eq 0 ]; then
220
+ result_clean "Docker security acceptable"
221
+ fi
222
+
223
+ # ============================================================
224
+ # CHECK 4 (origin 35): Exec safeBins Validation Bypass
225
+ # ============================================================
226
+ header 4 "Checking exec safeBins bypass (CVE-2026-28363)..."
227
+
228
+ SAFEBINS_ISSUES=0
229
+
230
+ if [ "$OPENCLAW_PRESENT" = true ]; then
231
+ if version_lt 2026 2 23; then
232
+ result_critical "OpenClaw v$OC_VERSION vulnerable to CVE-2026-28363 (safeBins bypass via sort --compress-prog). Update to v2026.2.23+"
233
+ SAFEBINS_ISSUES=$((SAFEBINS_ISSUES + 1))
234
+ fi
235
+
236
+ SAFE_BINS="$(get_oc_config "tools.exec.safeBins" "")"
237
+ if echo "$SAFE_BINS" | grep -q '"sort"' 2>/dev/null; then
238
+ log " INFO: 'sort' is in safeBins list (ensure v2026.2.23+)"
239
+ fi
240
+ fi
241
+
242
+ if [ "$SAFEBINS_ISSUES" -eq 0 ]; then
243
+ result_clean "Exec safeBins validation acceptable"
244
+ fi
245
+
246
+ # ============================================================
247
+ # CHECK 5 (origin 37): PATH Hijacking / Command Hijacking
248
+ # ============================================================
249
+ header 5 "Checking PATH hijacking risk (GHSA-jqpq)..."
250
+
251
+ PATH_ISSUES=0
252
+
253
+ IFS=':' read -r -a PATH_DIRS <<< "${PATH:-}"
254
+
255
+ for PDIR in "${PATH_DIRS[@]}"; do
256
+ [ -z "$PDIR" ] && continue
257
+ if [ -d "$PDIR" ] && [ -w "$PDIR" ]; then
258
+ case "$PDIR" in
259
+ "$HOME/bin"|"$HOME/.local/bin"|"$HOME/.cargo/bin"|"$HOME/go/bin")
260
+ continue
261
+ ;;
262
+ esac
263
+
264
+ for CMD in node python3 bash curl git ssh openclaw; do
265
+ if [ -f "$PDIR/$CMD" ] && [ -x "$PDIR/$CMD" ]; then
266
+ SYS_BIN="$(which -a "$CMD" 2>/dev/null | tail -1 || true)"
267
+ if [ -n "$SYS_BIN" ] && [ "$PDIR/$CMD" != "$SYS_BIN" ]; then
268
+ result_critical "PATH hijack: $PDIR/$CMD shadows system $SYS_BIN (GHSA-jqpq)"
269
+ PATH_ISSUES=$((PATH_ISSUES + 1))
270
+ fi
271
+ fi
272
+ done
273
+ fi
274
+ done
275
+
276
+ if [ -d "$WORKSPACE_DIR" ]; then
277
+ PLANTED="$(find "$WORKSPACE_DIR" -maxdepth 3 -type f \( -name "node" -o -name "python3" -o -name "bash" -o -name "curl" -o -name "git" \) 2>/dev/null | head -5 || true)"
278
+ if [ -n "$PLANTED" ]; then
279
+ while IFS= read -r pbin; do
280
+ [ -z "$pbin" ] && continue
281
+ if [ -x "$pbin" ]; then
282
+ result_critical "Planted executable in workspace: $pbin (PATH hijack vector)"
283
+ PATH_ISSUES=$((PATH_ISSUES + 1))
284
+ fi
285
+ done <<EOF
286
+ $PLANTED
287
+ EOF
288
+ fi
289
+ fi
290
+
291
+ if [ "$PATH_ISSUES" -eq 0 ]; then
292
+ result_clean "No PATH hijacking indicators found"
293
+ fi
294
+
295
+ # ============================================================
296
+ # CHECK 6 (origin 43): Exec-Approvals Shell Expansion Bypass
297
+ # ============================================================
298
+ header 6 "Checking exec-approvals shell expansion bypass (CVE-2026-28463)..."
299
+
300
+ SHEXP_ISSUES=0
301
+
302
+ if [ "$OPENCLAW_PRESENT" = true ]; then
303
+ if version_lt 2026 2 14; then
304
+ result_critical "Exec-approvals shell expansion bypass (CVE-2026-28463). Update to v2026.2.14+"
305
+ SHEXP_ISSUES=$((SHEXP_ISSUES + 1))
306
+ fi
307
+
308
+ SAFE_BINS="$(get_oc_config "tools.exec.safeBins" "")"
309
+ for RBIN in head tail grep cat; do
310
+ if echo "$SAFE_BINS" | grep -q "\"$RBIN\"" 2>/dev/null; then
311
+ log " INFO: '$RBIN' in safeBins - ensure v2026.2.14+"
312
+ fi
313
+ done
314
+ fi
315
+
316
+ if [ "$SHEXP_ISSUES" -eq 0 ]; then
317
+ result_clean "Exec-approvals shell expansion handling acceptable"
318
+ fi
319
+
320
+ # ============================================================
321
+ # CHECK 7 (origin 44): Approval Field Injection / Exec Gating Bypass
322
+ # ============================================================
323
+ header 7 "Checking approval field injection bypass (CVE-2026-28466)..."
324
+
325
+ AFI_ISSUES=0
326
+
327
+ if [ "$OPENCLAW_PRESENT" = true ]; then
328
+ if version_lt 2026 2 14; then
329
+ result_critical "Approval field injection bypass (CVE-2026-28466). Update to v2026.2.14+"
330
+ AFI_ISSUES=$((AFI_ISSUES + 1))
331
+ fi
332
+ fi
333
+
334
+ if [ "$AFI_ISSUES" -eq 0 ]; then
335
+ result_clean "Approval field sanitization acceptable"
336
+ fi
337
+
338
+ # ============================================================
339
+ # CHECK 8 (origin 42): Browser Control API Path Traversal
340
+ # ============================================================
341
+ header 8 "Checking browser control path traversal (CVE-2026-28462)..."
342
+
343
+ BCTRL_ISSUES=0
344
+
345
+ if [ "$OPENCLAW_PRESENT" = true ]; then
346
+ if version_lt 2026 2 13; then
347
+ result_critical "Browser control API path traversal (CVE-2026-28462). Update to v2026.2.13+"
348
+ BCTRL_ISSUES=$((BCTRL_ISSUES + 1))
349
+ fi
350
+ fi
351
+
352
+ if [ "$BCTRL_ISSUES" -eq 0 ]; then
353
+ result_clean "Browser control path handling acceptable"
354
+ fi
355
+
356
+ # ============================================================
357
+ # CHECK 9 (origin 47): TAR Archive Path Traversal
358
+ # ============================================================
359
+ header 9 "Checking TAR archive path traversal (CVE-2026-28453)..."
360
+
361
+ TAR_ISSUES=0
362
+
363
+ if [ "$OPENCLAW_PRESENT" = true ]; then
364
+ if version_lt 2026 2 14; then
365
+ result_critical "TAR archive path traversal (CVE-2026-28453). Update to v2026.2.14+"
366
+ TAR_ISSUES=$((TAR_ISSUES + 1))
367
+ fi
368
+ fi
369
+
370
+ if [ "$TAR_ISSUES" -eq 0 ]; then
371
+ result_clean "TAR archive handling acceptable"
372
+ fi
373
+
374
+ # ============================================================
375
+ # CHECK 10 (origin 19): Sandbox Configuration
376
+ # ============================================================
377
+ header 10 "Checking sandbox configuration..."
378
+
379
+ SANDBOX_ISSUES=0
380
+
381
+ if [ "$OPENCLAW_PRESENT" = true ]; then
382
+ SANDBOX_MODE="$(get_oc_config "sandbox.mode" "")"
383
+ if [ "$SANDBOX_MODE" = "off" ] || [ "$SANDBOX_MODE" = "none" ]; then
384
+ result_warn "Sandbox mode is disabled (recommended: mode='all')"
385
+ SANDBOX_ISSUES=$((SANDBOX_ISSUES + 1))
386
+ elif [ -n "$SANDBOX_MODE" ]; then
387
+ log " Sandbox mode: $SANDBOX_MODE"
388
+ fi
389
+
390
+ WORKSPACE_ACCESS="$(get_oc_config "sandbox.workspaceAccess" "")"
391
+ if [ "$WORKSPACE_ACCESS" = "rw" ]; then
392
+ log " INFO: Sandbox workspace access is read-write"
393
+ fi
394
+ fi
395
+
396
+ if [ "$SANDBOX_ISSUES" -eq 0 ]; then
397
+ result_clean "Sandbox configuration acceptable"
398
+ fi
399
+
400
+ # ============================================================
401
+ # CHECK 11 (origin 28): Node.js Version / Known CVEs
402
+ # ============================================================
403
+ header 11 "Checking Node.js version for known CVEs..."
404
+
405
+ NODE_ISSUES=0
406
+
407
+ if command -v node >/dev/null 2>&1; then
408
+ NODE_VER="$(node --version 2>/dev/null | sed 's/^v//')"
409
+ NODE_MAJOR="$(echo "$NODE_VER" | cut -d. -f1)"
410
+ NODE_MINOR="$(echo "$NODE_VER" | cut -d. -f2)"
411
+ NODE_PATCH="$(echo "$NODE_VER" | cut -d. -f3)"
412
+ log " Node.js version: v$NODE_VER"
413
+
414
+ if [ -n "$NODE_MAJOR" ] && [ "$NODE_MAJOR" -eq 22 ] 2>/dev/null && [ -n "$NODE_MINOR" ] && [ "$NODE_MINOR" -lt 12 ] 2>/dev/null; then
415
+ result_warn "Node.js v$NODE_VER is vulnerable to CVE-2026-21636 (permission model bypass). Upgrade to 22.12.0+"
416
+ NODE_ISSUES=$((NODE_ISSUES + 1))
417
+ elif [ -n "$NODE_MAJOR" ] && [ "$NODE_MAJOR" -lt 22 ] 2>/dev/null; then
418
+ result_warn "Node.js v$NODE_VER is below recommended v22 LTS"
419
+ NODE_ISSUES=$((NODE_ISSUES + 1))
420
+ fi
421
+ else
422
+ log " Node.js not found"
423
+ fi
424
+
425
+ if [ "$NODE_ISSUES" -eq 0 ]; then
426
+ result_clean "Node.js version acceptable"
427
+ fi
428
+
429
+ # ============================================================
430
+ # CHECK 12 (origin 24): Log Redaction Audit
431
+ # ============================================================
432
+ header 12 "Checking log redaction settings..."
433
+
434
+ LOG_ISSUES=0
435
+
436
+ if [ "$OPENCLAW_PRESENT" = true ]; then
437
+ REDACT="$(get_oc_config "logging.redactSensitive" "")"
438
+ if [ "$REDACT" = "off" ] || [ "$REDACT" = "false" ] || [ "$REDACT" = "none" ]; then
439
+ result_warn "Log redaction is disabled (sensitive data may leak to logs)"
440
+ LOG_ISSUES=$((LOG_ISSUES + 1))
441
+ elif [ -n "$REDACT" ]; then
442
+ log " Log redaction: $REDACT"
443
+ fi
444
+
445
+ GW_LOG="/tmp/openclaw"
446
+ if [ -d "$GW_LOG" ]; then
447
+ GW_LOG_PERMS="$(get_perm "$GW_LOG")"
448
+ if [ "$GW_LOG_PERMS" != "700" ] && [ "$GW_LOG_PERMS" != "750" ] && [ "$GW_LOG_PERMS" != "unknown" ]; then
449
+ result_warn "Gateway log dir /tmp/openclaw has permissions $GW_LOG_PERMS (recommended: 700)"
450
+ LOG_ISSUES=$((LOG_ISSUES + 1))
451
+ fi
452
+ fi
453
+ fi
454
+
455
+ if [ "$LOG_ISSUES" -eq 0 ]; then
456
+ result_clean "Log redaction settings acceptable"
457
+ fi
458
+
459
+ # ============================================================
460
+ # CHECK 13 (origin 22): Persistence Mechanism Scan
461
+ # ============================================================
462
+ header 13 "Scanning for unauthorized persistence mechanisms..."
463
+
464
+ PERSIST_ISSUES=0
465
+
466
+ if [ -d "$HOME/Library/LaunchAgents" ]; then
467
+ SUSPICIOUS_AGENTS="$(find "$HOME/Library/LaunchAgents" -name "*.plist" -exec grep -liE 'openclaw|clawdbot|moltbot' {} \; 2>/dev/null || true)"
468
+ if [ -n "$SUSPICIOUS_AGENTS" ]; then
469
+ log " LaunchAgents referencing OpenClaw-related strings:"
470
+ log "$SUSPICIOUS_AGENTS"
471
+
472
+ while IFS= read -r agent; do
473
+ [ -z "$agent" ] && continue
474
+ if ! grep -q "com.openclaw.security-dashboard" "$agent" 2>/dev/null; then
475
+ AGENT_LABEL="$(grep -A1 '<key>Label</key>' "$agent" 2>/dev/null | tail -1 | sed 's/.*<string>\(.*\)<\/string>.*/\1/' || true)"
476
+ result_warn "Unknown LaunchAgent: ${AGENT_LABEL:-unknown} ($(basename "$agent"))"
477
+ PERSIST_ISSUES=$((PERSIST_ISSUES + 1))
478
+ fi
479
+ done <<EOF
480
+ $SUSPICIOUS_AGENTS
481
+ EOF
482
+ fi
483
+ fi
484
+
485
+ CRON_ENTRIES="$(crontab -l 2>/dev/null | grep -ivE "${SELF_DIR_NAME}|#" | grep -iE 'openclaw|clawdbot|moltbot|curl.*\|.*sh|wget.*\|.*bash' || true)"
486
+ if [ -n "$CRON_ENTRIES" ]; then
487
+ result_warn "Suspicious cron entries found"
488
+ log "$CRON_ENTRIES"
489
+ PERSIST_ISSUES=$((PERSIST_ISSUES + 1))
490
+ fi
491
+
492
+ if command -v systemctl >/dev/null 2>&1; then
493
+ SYS_SERVICES="$(systemctl --user list-units --type=service --all 2>/dev/null | grep -iE 'openclaw|clawdbot|moltbot' | grep -v "$SELF_DIR_NAME" || true)"
494
+ if [ -n "$SYS_SERVICES" ]; then
495
+ log " User systemd services referencing OpenClaw-related strings:"
496
+ log "$SYS_SERVICES"
497
+ fi
498
+ fi
499
+
500
+ if [ "$PERSIST_ISSUES" -eq 0 ]; then
501
+ result_clean "No unauthorized persistence mechanisms"
502
+ fi