whitzard-claw 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +89 -0
  2. package/bin/whitzard-tui.js +73 -0
  3. package/bin/whitzard-webui.js +67 -0
  4. package/dist/tui/tui.js +38733 -0
  5. package/dist/webui/index.html +1235 -0
  6. package/dist/webui/server.js +876 -0
  7. package/ioc/c2-ips.txt +25 -0
  8. package/ioc/file-hashes.txt +13 -0
  9. package/ioc/malicious-domains.txt +46 -0
  10. package/ioc/malicious-hashes.txt +5 -0
  11. package/ioc/malicious-publishers.txt +34 -0
  12. package/ioc/malicious-skill-patterns.txt +87 -0
  13. package/package.json +50 -0
  14. package/scripts/check/access_control.sh +183 -0
  15. package/scripts/check/credential_storage.sh +222 -0
  16. package/scripts/check/execution_sandbox.sh +502 -0
  17. package/scripts/check/memory_poisoning.sh +334 -0
  18. package/scripts/check/network_exposure.sh +479 -0
  19. package/scripts/check/resource_cost.sh +182 -0
  20. package/scripts/check/supply_chain.sh +553 -0
  21. package/scripts/repair/access_control/_common.sh +249 -0
  22. package/scripts/repair/access_control/check_1.sh +28 -0
  23. package/scripts/repair/access_control/check_2.sh +27 -0
  24. package/scripts/repair/access_control/check_3.sh +23 -0
  25. package/scripts/repair/access_control/check_4.sh +23 -0
  26. package/scripts/repair/access_control/check_5.sh +20 -0
  27. package/scripts/repair/credential_storage/_common.sh +277 -0
  28. package/scripts/repair/credential_storage/check_1.sh +47 -0
  29. package/scripts/repair/credential_storage/check_2.sh +35 -0
  30. package/scripts/repair/credential_storage/check_3.sh +53 -0
  31. package/scripts/repair/credential_storage/logs/security-scan.log +15 -0
  32. package/scripts/repair/execution_sandbox/_common.sh +302 -0
  33. package/scripts/repair/execution_sandbox/check_1.sh +67 -0
  34. package/scripts/repair/execution_sandbox/check_10.sh +23 -0
  35. package/scripts/repair/execution_sandbox/check_11.sh +34 -0
  36. package/scripts/repair/execution_sandbox/check_12.sh +38 -0
  37. package/scripts/repair/execution_sandbox/check_13.sh +29 -0
  38. package/scripts/repair/execution_sandbox/check_2.sh +46 -0
  39. package/scripts/repair/execution_sandbox/check_3.sh +37 -0
  40. package/scripts/repair/execution_sandbox/check_4.sh +23 -0
  41. package/scripts/repair/execution_sandbox/check_5.sh +28 -0
  42. package/scripts/repair/execution_sandbox/check_6.sh +17 -0
  43. package/scripts/repair/execution_sandbox/check_7.sh +17 -0
  44. package/scripts/repair/execution_sandbox/check_8.sh +17 -0
  45. package/scripts/repair/execution_sandbox/check_9.sh +17 -0
  46. package/scripts/repair/execution_sandbox/logs/security-scan.log +10 -0
  47. package/scripts/repair/memory_poisoning/_common.sh +336 -0
  48. package/scripts/repair/memory_poisoning/check_1.sh +51 -0
  49. package/scripts/repair/memory_poisoning/check_2.sh +26 -0
  50. package/scripts/repair/memory_poisoning/check_3.sh +24 -0
  51. package/scripts/repair/memory_poisoning/check_4.sh +27 -0
  52. package/scripts/repair/memory_poisoning/check_5.sh +20 -0
  53. package/scripts/repair/network_exposure/_common.sh +330 -0
  54. package/scripts/repair/network_exposure/check_1.sh +86 -0
  55. package/scripts/repair/network_exposure/check_10.sh +16 -0
  56. package/scripts/repair/network_exposure/check_11.sh +31 -0
  57. package/scripts/repair/network_exposure/check_12.sh +24 -0
  58. package/scripts/repair/network_exposure/check_2.sh +26 -0
  59. package/scripts/repair/network_exposure/check_3.sh +43 -0
  60. package/scripts/repair/network_exposure/check_4.sh +23 -0
  61. package/scripts/repair/network_exposure/check_5.sh +16 -0
  62. package/scripts/repair/network_exposure/check_6.sh +98 -0
  63. package/scripts/repair/network_exposure/check_7.sh +35 -0
  64. package/scripts/repair/network_exposure/check_8.sh +19 -0
  65. package/scripts/repair/network_exposure/check_9.sh +19 -0
  66. package/scripts/repair/resource_cost/_common.sh +303 -0
  67. package/scripts/repair/resource_cost/check_1.sh +16 -0
  68. package/scripts/repair/resource_cost/check_2.sh +16 -0
  69. package/scripts/repair/resource_cost/check_3.sh +23 -0
  70. package/scripts/repair/supply_chain/_common.sh +222 -0
  71. package/scripts/repair/supply_chain/check_1.sh +95 -0
  72. package/scripts/repair/supply_chain/check_10.sh +60 -0
  73. package/scripts/repair/supply_chain/check_11.sh +63 -0
  74. package/scripts/repair/supply_chain/check_12.sh +36 -0
  75. package/scripts/repair/supply_chain/check_13.sh +44 -0
  76. package/scripts/repair/supply_chain/check_14.sh +33 -0
  77. package/scripts/repair/supply_chain/check_15.sh +33 -0
  78. package/scripts/repair/supply_chain/check_16.sh +34 -0
  79. package/scripts/repair/supply_chain/check_17.sh +61 -0
  80. package/scripts/repair/supply_chain/check_18.sh +62 -0
  81. package/scripts/repair/supply_chain/check_2.sh +93 -0
  82. package/scripts/repair/supply_chain/check_3.sh +78 -0
  83. package/scripts/repair/supply_chain/check_4.sh +72 -0
  84. package/scripts/repair/supply_chain/check_5.sh +73 -0
  85. package/scripts/repair/supply_chain/check_6.sh +81 -0
  86. package/scripts/repair/supply_chain/check_7.sh +52 -0
  87. package/scripts/repair/supply_chain/check_8.sh +71 -0
  88. package/scripts/repair/supply_chain/check_9.sh +78 -0
  89. package/scripts/repair/supply_chain/logs/security-scan.log +77 -0
  90. package/scripts/scan.sh +228 -0
  91. package/webui/index.html +1235 -0
@@ -0,0 +1,479 @@
1
+ # shellcheck shell=bash
2
+
3
+ export PATH="$HOME/.local/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
4
+
5
+ # ------------------------------------------------------------
6
+ # Shared helpers
7
+ # ------------------------------------------------------------
8
+
9
+ OPENCLAW_PRESENT=false
10
+ if command -v openclaw >/dev/null 2>&1; then
11
+ OPENCLAW_PRESENT=true
12
+ fi
13
+
14
+ OC_VERSION="unknown"
15
+ PARSED_OC_VERSION=""
16
+ OC_MAJOR=""
17
+ OC_MINOR=""
18
+ OC_PATCH=""
19
+
20
+ if [ "$OPENCLAW_PRESENT" = true ]; then
21
+ OC_VERSION="$(run_with_timeout 5 openclaw --version 2>/dev/null || echo "unknown")"
22
+ PARSED_OC_VERSION="$(echo "$OC_VERSION" | grep -oE '[0-9]{4}\.[0-9]+\.[0-9]+' | head -1 || true)"
23
+ if [ -n "$PARSED_OC_VERSION" ]; then
24
+ OC_MAJOR="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f1)"
25
+ OC_MINOR="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f2)"
26
+ OC_PATCH="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f3)"
27
+ fi
28
+ fi
29
+
30
+ get_oc_config() {
31
+ # Usage: get_oc_config <key> <default>
32
+ local key="$1"
33
+ local default_val="${2:-}"
34
+ if [ "$OPENCLAW_PRESENT" = true ]; then
35
+ run_with_timeout 10 openclaw config get "$key" 2>/dev/null || echo "$default_val"
36
+ else
37
+ echo "$default_val"
38
+ fi
39
+ }
40
+
41
+ normalize_config_val() {
42
+ # Normalize null/undefined-ish values to empty string
43
+ local val="${1:-}"
44
+ case "$val" in
45
+ null|undefined|"")
46
+ echo ""
47
+ ;;
48
+ *)
49
+ echo "$val"
50
+ ;;
51
+ esac
52
+ }
53
+
54
+ version_ge() {
55
+ # Usage: version_ge <major> <minor> <patch>
56
+ # returns 0 if OC_VERSION >= target, else 1
57
+ local t_major="$1"
58
+ local t_minor="$2"
59
+ local t_patch="$3"
60
+
61
+ if [ -z "$PARSED_OC_VERSION" ]; then
62
+ return 1
63
+ fi
64
+
65
+ if [ "$OC_MAJOR" -gt "$t_major" ] 2>/dev/null; then
66
+ return 0
67
+ fi
68
+ if [ "$OC_MAJOR" -lt "$t_major" ] 2>/dev/null; then
69
+ return 1
70
+ fi
71
+
72
+ if [ "$OC_MINOR" -gt "$t_minor" ] 2>/dev/null; then
73
+ return 0
74
+ fi
75
+ if [ "$OC_MINOR" -lt "$t_minor" ] 2>/dev/null; then
76
+ return 1
77
+ fi
78
+
79
+ if [ "$OC_PATCH" -ge "$t_patch" ] 2>/dev/null; then
80
+ return 0
81
+ fi
82
+
83
+ return 1
84
+ }
85
+
86
+ version_lt() {
87
+ # Usage: version_lt <major> <minor> <patch>
88
+ # returns 0 if OC_VERSION < target, else 1
89
+ if [ -z "$PARSED_OC_VERSION" ]; then
90
+ return 1
91
+ fi
92
+ if version_ge "$1" "$2" "$3"; then
93
+ return 1
94
+ fi
95
+ return 0
96
+ }
97
+
98
+ detect_gateway_port() {
99
+ local raw
100
+ raw="$(get_oc_config "gateway.port" "18789")"
101
+ raw="$(echo "$raw" | grep -oE '[0-9]+' | head -1 || true)"
102
+ if [ -z "$raw" ]; then
103
+ raw="18789"
104
+ fi
105
+ echo "$raw"
106
+ }
107
+
108
+ GW_PORT="$(detect_gateway_port)"
109
+
110
+ # ============================================================
111
+ # CHECK 1 (origin 13): Gateway Security Configuration
112
+ # ============================================================
113
+ header 1 "Auditing gateway security configuration..."
114
+
115
+ GW_ISSUES=0
116
+
117
+ if [ "$OPENCLAW_PRESENT" = true ]; then
118
+ log " OpenClaw version: $OC_VERSION"
119
+
120
+ BIND="$(get_oc_config "gateway.bind" "unknown")"
121
+ if [ "$BIND" = "lan" ] || [ "$BIND" = "0.0.0.0" ]; then
122
+ result_warn "Gateway bound to LAN ($BIND) - accessible from network"
123
+ GW_ISSUES=$((GW_ISSUES + 1))
124
+ fi
125
+
126
+ AUTH_MODE="$(get_oc_config "gateway.auth.mode" "unknown")"
127
+ if [ "$AUTH_MODE" = "none" ] || [ "$AUTH_MODE" = "off" ]; then
128
+ result_critical "Gateway authentication is disabled"
129
+ GW_ISSUES=$((GW_ISSUES + 1))
130
+ fi
131
+ else
132
+ log " openclaw command not found"
133
+ fi
134
+
135
+ if [ "$GW_ISSUES" -eq 0 ]; then
136
+ result_clean "Gateway configuration acceptable"
137
+ fi
138
+
139
+ # ============================================================
140
+ # CHECK 2 (origin 14): WebSocket Origin Validation
141
+ # ============================================================
142
+ header 2 "Checking WebSocket security (CVE-2026-25253)..."
143
+
144
+ if command -v curl >/dev/null 2>&1; then
145
+ WS_RAW="$(curl -s -o /dev/null -w "%{http_code}" \
146
+ -H "Connection: Upgrade" \
147
+ -H "Upgrade: websocket" \
148
+ -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
149
+ -H "Sec-WebSocket-Version: 13" \
150
+ -H "Origin: http://evil.attacker.com" \
151
+ --connect-timeout 3 --max-time 5 \
152
+ "http://127.0.0.1:${GW_PORT}/" 2>/dev/null || echo "000")"
153
+ WS_TEST="$(echo "$WS_RAW" | head -c 3)"
154
+ else
155
+ WS_TEST="000"
156
+ fi
157
+
158
+ if [ "$WS_TEST" = "101" ]; then
159
+ PATCHED_WS=false
160
+ if version_ge 2026 2 0; then
161
+ PATCHED_WS=true
162
+ elif version_ge 2026 1 29; then
163
+ PATCHED_WS=true
164
+ fi
165
+
166
+ HAS_GW_AUTH="$(get_oc_config "gateway.auth.mode" "")"
167
+ if [ "$PATCHED_WS" = true ] && [ -n "$HAS_GW_AUTH" ] && [ "$HAS_GW_AUTH" != "none" ] && [ "$HAS_GW_AUTH" != "off" ]; then
168
+ result_clean "WebSocket: v$OC_VERSION patched + gateway auth ($HAS_GW_AUTH) - CVE-2026-25253 mitigated"
169
+ elif [ "$PATCHED_WS" = true ]; then
170
+ result_warn "WebSocket: v$OC_VERSION patched but gateway auth is weak/missing"
171
+ else
172
+ result_critical "Gateway accepts WebSocket from arbitrary origins (CVE-2026-25253 may be unpatched)"
173
+ fi
174
+ elif [ "$WS_TEST" = "000" ]; then
175
+ log " Gateway not reachable on port $GW_PORT (may be normal)"
176
+ result_clean "WebSocket test inconclusive (gateway not reachable)"
177
+ elif [ "$WS_TEST" = "403" ] || [ "$WS_TEST" = "401" ]; then
178
+ result_clean "WebSocket origin validation active (HTTP $WS_TEST rejected)"
179
+ else
180
+ result_clean "WebSocket responded HTTP $WS_TEST"
181
+ fi
182
+
183
+ # ============================================================
184
+ # CHECK 3 (origin 25): Reverse Proxy / Localhost Trust Bypass
185
+ # ============================================================
186
+ header 3 "Checking for reverse proxy localhost trust bypass..."
187
+
188
+ PROXY_ISSUES=0
189
+
190
+ if [ "$OPENCLAW_PRESENT" = true ]; then
191
+ BIND_ADDR="$(get_oc_config "gateway.bind" "")"
192
+ TRUSTED_PROXIES="$(get_oc_config "gateway.trustedProxies" "")"
193
+ DISABLE_DEVICE_AUTH="$(get_oc_config "gateway.dangerouslyDisableDeviceAuth" "")"
194
+
195
+ if [ "$DISABLE_DEVICE_AUTH" = "true" ]; then
196
+ result_critical "Device authentication is disabled (dangerouslyDisableDeviceAuth=true)"
197
+ PROXY_ISSUES=$((PROXY_ISSUES + 1))
198
+ fi
199
+
200
+ if [ "$BIND_ADDR" = "lan" ] || [ "$BIND_ADDR" = "0.0.0.0" ]; then
201
+ if [ -z "$TRUSTED_PROXIES" ] || [ "$TRUSTED_PROXIES" = "null" ] || [ "$TRUSTED_PROXIES" = "[]" ]; then
202
+ result_warn "Gateway on LAN without trustedProxies - localhost trust bypass risk"
203
+ PROXY_ISSUES=$((PROXY_ISSUES + 1))
204
+ fi
205
+ fi
206
+ fi
207
+
208
+ if [ "$PROXY_ISSUES" -eq 0 ]; then
209
+ result_clean "No reverse proxy bypass risk"
210
+ fi
211
+
212
+ # ============================================================
213
+ # CHECK 4 (origin 33): ClawJacked Brute-Force Protection
214
+ # ============================================================
215
+ header 4 "Checking ClawJacked brute-force protection..."
216
+
217
+ CLAWJACKED_ISSUES=0
218
+
219
+ if [ "$OPENCLAW_PRESENT" = true ]; then
220
+ if [ -n "$PARSED_OC_VERSION" ]; then
221
+ CLAWJACKED_SAFE=false
222
+ if version_ge 2026 3 0; then
223
+ CLAWJACKED_SAFE=true
224
+ elif version_ge 2026 2 26; then
225
+ CLAWJACKED_SAFE=true
226
+ fi
227
+
228
+ if [ "$CLAWJACKED_SAFE" = false ]; then
229
+ result_critical "OpenClaw v$OC_VERSION vulnerable to ClawJacked (WebSocket brute-force). Update to v2026.2.26+"
230
+ CLAWJACKED_ISSUES=$((CLAWJACKED_ISSUES + 1))
231
+ fi
232
+ else
233
+ result_warn "Could not parse OpenClaw version from: $OC_VERSION"
234
+ CLAWJACKED_ISSUES=$((CLAWJACKED_ISSUES + 1))
235
+ fi
236
+
237
+ GW_AUTH_TOKEN="$(get_oc_config "gateway.auth.token" "")"
238
+ if [ -z "$GW_AUTH_TOKEN" ] || [ "$GW_AUTH_TOKEN" = "null" ]; then
239
+ result_warn "No gateway auth token set (ClawJacked can brute-force weak/default credentials more easily)"
240
+ CLAWJACKED_ISSUES=$((CLAWJACKED_ISSUES + 1))
241
+ fi
242
+
243
+ RATE_LIMIT="$(get_oc_config "gateway.auth.rateLimit" "")"
244
+ if [ "$RATE_LIMIT" = "off" ] || [ "$RATE_LIMIT" = "false" ]; then
245
+ result_warn "Gateway auth rate limiting is disabled (enables brute-force attacks)"
246
+ CLAWJACKED_ISSUES=$((CLAWJACKED_ISSUES + 1))
247
+ fi
248
+ fi
249
+
250
+ if [ "$CLAWJACKED_ISSUES" -eq 0 ]; then
251
+ result_clean "ClawJacked protections in place"
252
+ fi
253
+
254
+ # ============================================================
255
+ # CHECK 5 (origin 41): Browser Relay CDP Unauthenticated Access
256
+ # ============================================================
257
+ header 5 "Checking Browser Relay CDP auth (CVE-2026-28458)..."
258
+
259
+ CDP_ISSUES=0
260
+
261
+ if command -v lsof >/dev/null 2>&1; then
262
+ CDP_LISTEN="$(lsof -iTCP:18792 -sTCP:LISTEN -nP 2>/dev/null | grep -v '^COMMAND' || true)"
263
+ if [ -n "$CDP_LISTEN" ]; then
264
+ log " Browser Relay is listening on port 18792"
265
+ if [ -n "$PARSED_OC_VERSION" ]; then
266
+ if version_lt 2026 2 1; then
267
+ result_critical "Browser Relay /cdp endpoint may be unauthenticated (CVE-2026-28458). Update to v2026.2.1+"
268
+ CDP_ISSUES=$((CDP_ISSUES + 1))
269
+ fi
270
+ fi
271
+ fi
272
+ fi
273
+
274
+ if [ "$CDP_ISSUES" -eq 0 ]; then
275
+ result_clean "Browser Relay CDP auth acceptable"
276
+ fi
277
+
278
+ # ============================================================
279
+ # CHECK 6 (origin 31): Internet Exposure Detection
280
+ # ============================================================
281
+ header 6 "Checking for internet exposure of gateway..."
282
+
283
+ EXPOSURE_ISSUES=0
284
+
285
+ if command -v lsof >/dev/null 2>&1; then
286
+ GW_LISTEN="$(lsof -i ":${GW_PORT}" -nP 2>/dev/null | grep LISTEN | awk '{print $9}' | head -5 || true)"
287
+ if [ -n "$GW_LISTEN" ]; then
288
+ NON_LOCAL="$(echo "$GW_LISTEN" | grep -vE '127\.0\.0\.1|localhost|\[::1\]' || true)"
289
+ if [ -n "$NON_LOCAL" ]; then
290
+ result_warn "Gateway listening on non-loopback interface"
291
+ log "$GW_LISTEN"
292
+ EXPOSURE_ISSUES=$((EXPOSURE_ISSUES + 1))
293
+ fi
294
+
295
+ WILDCARD="$(echo "$GW_LISTEN" | grep '\*:' || true)"
296
+ if [ -n "$WILDCARD" ]; then
297
+ result_warn "Gateway bound to all interfaces (*:$GW_PORT) - potentially internet-exposed"
298
+ EXPOSURE_ISSUES=$((EXPOSURE_ISSUES + 1))
299
+ fi
300
+ fi
301
+ fi
302
+
303
+ if [ "$EXPOSURE_ISSUES" -eq 0 ]; then
304
+ result_clean "Gateway not exposed to external network"
305
+ fi
306
+
307
+ # ============================================================
308
+ # CHECK 7 (origin 20): mDNS/Bonjour Exposure
309
+ # ============================================================
310
+ header 7 "Checking mDNS/Bonjour discovery settings..."
311
+
312
+ MDNS_ISSUES=0
313
+
314
+ if [ "$OPENCLAW_PRESENT" = true ]; then
315
+ MDNS_MODE="$(get_oc_config "discovery.mdns.mode" "")"
316
+ if [ "$MDNS_MODE" = "full" ]; then
317
+ result_warn "mDNS broadcasting in 'full' mode (exposes paths, SSH port)"
318
+ MDNS_ISSUES=$((MDNS_ISSUES + 1))
319
+ elif [ -n "$MDNS_MODE" ]; then
320
+ log " mDNS mode: $MDNS_MODE"
321
+ fi
322
+ fi
323
+
324
+ if [ "$MDNS_ISSUES" -eq 0 ]; then
325
+ result_clean "mDNS configuration acceptable"
326
+ fi
327
+
328
+ # ============================================================
329
+ # CHECK 8 (origin 34): SSRF Protection
330
+ # ============================================================
331
+ header 8 "Checking SSRF protections (CVE-2026-26322, CVE-2026-27488)..."
332
+
333
+ SSRF_ISSUES=0
334
+
335
+ if [ "$OPENCLAW_PRESENT" = true ]; then
336
+ if [ -n "$PARSED_OC_VERSION" ]; then
337
+ if version_lt 2026 2 14; then
338
+ result_critical "OpenClaw v$OC_VERSION vulnerable to gateway SSRF (CVE-2026-26322) and cron SSRF (CVE-2026-27488). Update to v2026.2.19+"
339
+ SSRF_ISSUES=$((SSRF_ISSUES + 1))
340
+ elif version_lt 2026 2 19; then
341
+ result_warn "OpenClaw v$OC_VERSION vulnerable to cron webhook SSRF (CVE-2026-27488). Update to v2026.2.19+"
342
+ SSRF_ISSUES=$((SSRF_ISSUES + 1))
343
+ fi
344
+ fi
345
+
346
+ CRON_CONFIG="$(get_oc_config "cron" "")"
347
+ if echo "$CRON_CONFIG" | grep -qiE '169\.254\.|127\.0\.0\.1|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|metadata\.google|metadata\.aws'; then
348
+ result_warn "Cron webhook targets internal/metadata endpoints (SSRF risk)"
349
+ SSRF_ISSUES=$((SSRF_ISSUES + 1))
350
+ fi
351
+ fi
352
+
353
+ if [ "$SSRF_ISSUES" -eq 0 ]; then
354
+ result_clean "SSRF protections acceptable"
355
+ fi
356
+
357
+ # ============================================================
358
+ # CHECK 9 (origin 36): ACP Permission Auto-Approval
359
+ # ============================================================
360
+ header 9 "Checking ACP auto-approval bypass (GHSA-7jx5)..."
361
+
362
+ ACP_ISSUES=0
363
+
364
+ if [ "$OPENCLAW_PRESENT" = true ]; then
365
+ if [ -n "$PARSED_OC_VERSION" ]; then
366
+ if version_lt 2026 2 23; then
367
+ result_warn "OpenClaw v$OC_VERSION vulnerable to GHSA-7jx5 (ACP auto-approval bypass). Update to v2026.2.23+"
368
+ ACP_ISSUES=$((ACP_ISSUES + 1))
369
+ fi
370
+ fi
371
+
372
+ ACP_AUTO="$(get_oc_config "acp.autoApprove" "")"
373
+ if [ "$ACP_AUTO" = "true" ] || [ "$ACP_AUTO" = "all" ]; then
374
+ result_warn "ACP auto-approve is enabled (tool calls bypass interactive approval)"
375
+ ACP_ISSUES=$((ACP_ISSUES + 1))
376
+ fi
377
+ fi
378
+
379
+ if [ "$ACP_ISSUES" -eq 0 ]; then
380
+ result_clean "ACP permission controls acceptable"
381
+ fi
382
+
383
+ # ============================================================
384
+ # CHECK 10 (origin 39/45): Deep Link + Sandbox Browser Bridge
385
+ # ============================================================
386
+ header 10 "Checking deep link truncation and sandbox browser bridge auth..."
387
+
388
+ LOCAL_BRIDGE_ISSUES=0
389
+
390
+ # ---- Deep link truncation (origin 39)
391
+ if [ "$(uname -s)" = "Darwin" ]; then
392
+ if [ -n "$PARSED_OC_VERSION" ] && version_lt 2026 2 14; then
393
+ result_warn "OpenClaw v$OC_VERSION vulnerable to deep link truncation (CVE-2026-26320). Update to v2026.2.14+"
394
+ LOCAL_BRIDGE_ISSUES=$((LOCAL_BRIDGE_ISSUES + 1))
395
+ fi
396
+
397
+ if [ -d "$LOG_DIR" ]; then
398
+ LONG_LINKS="$(grep -rl 'openclaw://' "$LOG_DIR" 2>/dev/null | head -3 || true)"
399
+ if [ -n "$LONG_LINKS" ]; then
400
+ while IFS= read -r lfile; do
401
+ [ -z "$lfile" ] && continue
402
+ if grep -E 'openclaw://[^ ]{240,}' "$lfile" >/dev/null 2>&1; then
403
+ result_warn "Long deep link (>240 chars) found in logs - possible CVE-2026-26320 exploit attempt"
404
+ LOCAL_BRIDGE_ISSUES=$((LOCAL_BRIDGE_ISSUES + 1))
405
+ break
406
+ fi
407
+ done <<EOF
408
+ $LONG_LINKS
409
+ EOF
410
+ fi
411
+ fi
412
+ else
413
+ log " Not macOS, skipping deep-link truncation check"
414
+ fi
415
+
416
+ # ---- Sandbox browser bridge auth bypass (origin 45)
417
+ if [ -n "$PARSED_OC_VERSION" ] && version_lt 2026 2 14; then
418
+ result_warn "Sandbox browser bridge may be unauthenticated (CVE-2026-28468). Update to v2026.2.14+"
419
+ LOCAL_BRIDGE_ISSUES=$((LOCAL_BRIDGE_ISSUES + 1))
420
+ fi
421
+
422
+ if [ "$LOCAL_BRIDGE_ISSUES" -eq 0 ]; then
423
+ result_clean "Deep link and sandbox browser bridge protections acceptable"
424
+ fi
425
+
426
+ # ============================================================
427
+ # CHECK 11 (new / GW-003): Weak gateway auth token/password
428
+ # ============================================================
429
+ header 11 "Checking gateway auth token/password strength..."
430
+
431
+ GW_WEAK_AUTH_ISSUES=0
432
+
433
+ if [ "$OPENCLAW_PRESENT" = true ]; then
434
+ AUTH_MODE="$(normalize_config_val "$(get_oc_config "gateway.auth.mode" "")")"
435
+
436
+ GW_AUTH_TOKEN_CANDIDATE="$(normalize_config_val "$(get_oc_config "gateway.auth.token" "")")"
437
+ GW_AUTH_PASSWORD_CANDIDATE="$(normalize_config_val "$(get_oc_config "gateway.auth.password" "")")"
438
+ GW_AUTH_LEGACY_CANDIDATE="$(normalize_config_val "$(get_oc_config "gateway.authToken" "")")"
439
+
440
+ GW_AUTH_SECRET=""
441
+ if [ -n "$GW_AUTH_TOKEN_CANDIDATE" ]; then
442
+ GW_AUTH_SECRET="$GW_AUTH_TOKEN_CANDIDATE"
443
+ elif [ -n "$GW_AUTH_PASSWORD_CANDIDATE" ]; then
444
+ GW_AUTH_SECRET="$GW_AUTH_PASSWORD_CANDIDATE"
445
+ elif [ -n "$GW_AUTH_LEGACY_CANDIDATE" ]; then
446
+ GW_AUTH_SECRET="$GW_AUTH_LEGACY_CANDIDATE"
447
+ fi
448
+
449
+ if [ "$AUTH_MODE" = "token" ] || [ "$AUTH_MODE" = "password" ]; then
450
+ GW_SECRET_LEN="$(printf "%s" "$GW_AUTH_SECRET" | wc -c | tr -d ' ' || echo "0")"
451
+ if [ "$GW_SECRET_LEN" -gt 0 ] 2>/dev/null && [ "$GW_SECRET_LEN" -lt 32 ] 2>/dev/null; then
452
+ result_warn "Weak gateway authentication token/password (${GW_SECRET_LEN} chars). Minimum 32 recommended"
453
+ GW_WEAK_AUTH_ISSUES=$((GW_WEAK_AUTH_ISSUES + 1))
454
+ fi
455
+ fi
456
+ fi
457
+
458
+ if [ "$GW_WEAK_AUTH_ISSUES" -eq 0 ]; then
459
+ result_clean "Gateway auth token/password length acceptable"
460
+ fi
461
+
462
+ # ============================================================
463
+ # CHECK 12 (new / GW-006): Gateway TLS enabled
464
+ # ============================================================
465
+ header 12 "Checking whether gateway TLS is enabled..."
466
+
467
+ GW_TLS_ISSUES=0
468
+
469
+ if [ "$OPENCLAW_PRESENT" = true ]; then
470
+ GW_TLS_ENABLED="$(normalize_config_val "$(get_oc_config "gateway.tls.enabled" "")")"
471
+ if [ "$GW_TLS_ENABLED" != "true" ]; then
472
+ result_warn "TLS not enabled on gateway (gateway.tls.enabled is not true) - credentials and traffic may be sent in plaintext"
473
+ GW_TLS_ISSUES=$((GW_TLS_ISSUES + 1))
474
+ fi
475
+ fi
476
+
477
+ if [ "$GW_TLS_ISSUES" -eq 0 ]; then
478
+ result_clean "Gateway TLS is enabled"
479
+ fi
@@ -0,0 +1,182 @@
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
+
22
+ if [ -n "$PARSED_OC_VERSION" ]; then
23
+ OC_MAJOR="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f1)"
24
+ OC_MINOR="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f2)"
25
+ OC_PATCH="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f3)"
26
+ fi
27
+ fi
28
+
29
+ version_ge() {
30
+
31
+ local t_major="$1"
32
+ local t_minor="$2"
33
+ local t_patch="$3"
34
+
35
+ if [ -z "$PARSED_OC_VERSION" ]; then
36
+ return 1
37
+ fi
38
+
39
+ if [ "$OC_MAJOR" -gt "$t_major" ] 2>/dev/null; then
40
+ return 0
41
+ fi
42
+ if [ "$OC_MAJOR" -lt "$t_major" ] 2>/dev/null; then
43
+ return 1
44
+ fi
45
+
46
+ if [ "$OC_MINOR" -gt "$t_minor" ] 2>/dev/null; then
47
+ return 0
48
+ fi
49
+ if [ "$OC_MINOR" -lt "$t_minor" ] 2>/dev/null; then
50
+ return 1
51
+ fi
52
+
53
+ if [ "$OC_PATCH" -ge "$t_patch" ] 2>/dev/null; then
54
+ return 0
55
+ fi
56
+
57
+ return 1
58
+ }
59
+
60
+ version_lt() {
61
+
62
+ if [ -z "$PARSED_OC_VERSION" ]; then
63
+ return 1
64
+ fi
65
+
66
+ if version_ge "$1" "$2" "$3"; then
67
+ return 1
68
+ fi
69
+
70
+ return 0
71
+ }
72
+
73
+ # ------------------------------------------------------------
74
+ # Cron frequency check
75
+ # ------------------------------------------------------------
76
+
77
+ has_high_freq_cron() {
78
+
79
+ grep -Eq '^[[:space:]]*(\*[[:space:]]+\*[[:space:]]+\*[[:space:]]+\*[[:space:]]+\*|\*/[1-4][[:space:]]+\*[[:space:]]+\*[[:space:]]+\*[[:space:]]+\*)' "$1" 2>/dev/null
80
+
81
+ }
82
+
83
+ TOTAL_TOKENS=0
84
+ TOTAL_COST=0
85
+ HOURLY_COST=0
86
+
87
+ # ============================================================
88
+ # CHECK 1: Webhook DoS
89
+ # ============================================================
90
+
91
+ header 1 "Checking webhook DoS / oversized payloads (CVE-2026-28478)..."
92
+
93
+ WDOS_ISSUES=0
94
+
95
+ if [ "$OPENCLAW_PRESENT" = true ]; then
96
+
97
+ if version_lt 2026 2 13; then
98
+
99
+ result_warn "Webhook handlers may lack strict body size/time limits (CVE-2026-28478). Update to v2026.2.13+"
100
+
101
+ WDOS_ISSUES=$((WDOS_ISSUES+1))
102
+ fi
103
+
104
+ else
105
+
106
+ log "openclaw command not found"
107
+
108
+ fi
109
+
110
+ if [ "$WDOS_ISSUES" -eq 0 ]; then
111
+ result_clean "Webhook body limits acceptable"
112
+ fi
113
+
114
+
115
+ # ============================================================
116
+ # CHECK 2: fetchWithGuard DoS
117
+ # ============================================================
118
+
119
+ header 2 "Checking fetchWithGuard memory DoS (CVE-2026-29609)..."
120
+
121
+ FWG_ISSUES=0
122
+
123
+ if [ "$OPENCLAW_PRESENT" = true ]; then
124
+
125
+ if version_lt 2026 2 14; then
126
+
127
+ result_warn "fetchWithGuard may be vulnerable to memory exhaustion DoS (CVE-2026-29609). Update to v2026.2.14+"
128
+
129
+ FWG_ISSUES=$((FWG_ISSUES+1))
130
+ fi
131
+
132
+ else
133
+
134
+ log "openclaw command not found"
135
+
136
+ fi
137
+
138
+ if [ "$FWG_ISSUES" -eq 0 ]; then
139
+ result_clean "fetchWithGuard memory handling acceptable"
140
+ fi
141
+
142
+
143
+ # ============================================================
144
+ # CHECK 3: High frequency cron
145
+ # ============================================================
146
+
147
+ header 3 "Checking high-frequency scheduled agent invocations (> every 5 minutes)..."
148
+
149
+ CRON_ISSUES=0
150
+
151
+ LOCAL_CRON_FILE="$OPENCLAW_DIR/crontab"
152
+
153
+ if [ -f "$LOCAL_CRON_FILE" ]; then
154
+
155
+ if has_high_freq_cron "$LOCAL_CRON_FILE"; then
156
+
157
+ result_warn "High-frequency schedules found in $LOCAL_CRON_FILE (more often than every 5 minutes)"
158
+
159
+ CRON_ISSUES=$((CRON_ISSUES+1))
160
+ fi
161
+ fi
162
+
163
+ if command -v crontab >/dev/null 2>&1; then
164
+
165
+ TMP_CRON=$(mktemp)
166
+
167
+ if crontab -l >"$TMP_CRON" 2>/dev/null; then
168
+
169
+ if has_high_freq_cron "$TMP_CRON"; then
170
+
171
+ result_warn "High-frequency schedules found in user crontab (more often than every 5 minutes)"
172
+
173
+ CRON_ISSUES=$((CRON_ISSUES+1))
174
+ fi
175
+ fi
176
+
177
+ rm -f "$TMP_CRON"
178
+ fi
179
+
180
+ if [ "$CRON_ISSUES" -eq 0 ]; then
181
+ result_clean "No high-frequency scheduled invocations detected"
182
+ fi