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,249 @@
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
+ # Usage: lookup_malicious_hash_campaign <sha256>
182
+ local needle
183
+ needle="$(printf "%s" "${1:-}" | tr '[:upper:]' '[:lower:]')"
184
+ [ -n "$needle" ] || return 1
185
+
186
+ load_hash_iocs | while IFS='|' read -r hash_val campaign rest; do
187
+ hash_val="$(printf "%s" "$hash_val" | tr '[:upper:]' '[:lower:]')"
188
+ if [ "$hash_val" = "$needle" ]; then
189
+ printf "%s\n" "${campaign:-unknown}"
190
+ return 0
191
+ fi
192
+ done
193
+ }
194
+
195
+ extract_github_account_age_days() {
196
+ # Best-effort local metadata extraction only.
197
+ # We cannot reliably infer GitHub account age from filesystem alone unless
198
+ # the skill metadata explicitly records it.
199
+ local skill_dir="$1"
200
+ local val=""
201
+
202
+ for meta in "$skill_dir/package.json" "$skill_dir/config.json" "$skill_dir/SKILL.md"; do
203
+ [ -f "$meta" ] || continue
204
+
205
+ # JSON-ish keys
206
+ val="$(grep -iEo '"(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)"[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
207
+ if [ -n "$val" ]; then
208
+ printf "%s\n" "$val"
209
+ return 0
210
+ fi
211
+
212
+ # YAML / markdown frontmatter-ish keys
213
+ val="$(grep -iE '^(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
214
+ if [ -n "$val" ]; then
215
+ printf "%s\n" "$val"
216
+ return 0
217
+ fi
218
+ done
219
+
220
+ return 1
221
+ }
222
+
223
+ OPENCLAW_PRESENT=false
224
+ if command -v openclaw >/dev/null 2>&1; then
225
+ OPENCLAW_PRESENT=true
226
+ fi
227
+
228
+ get_oc_config() {
229
+ # Usage: get_oc_config <key> <default>
230
+ local key="$1"
231
+ local default_val="${2:-}"
232
+ if [ "$OPENCLAW_PRESENT" = true ]; then
233
+ run_with_timeout 10 openclaw config get "$key" 2>/dev/null || echo "$default_val"
234
+ else
235
+ echo "$default_val"
236
+ fi
237
+ }
238
+
239
+ normalize_config_val() {
240
+ local val="${1:-}"
241
+ case "$val" in
242
+ null|undefined|"")
243
+ echo ""
244
+ ;;
245
+ *)
246
+ echo "$val"
247
+ ;;
248
+ esac
249
+ }
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+ # CHECK 1 (origin 17 / covers AC-001 partially + AC-003 partially)
3
+ # DM Policy Audit
4
+ # Usage: ./check_1.sh
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ source "$SCRIPT_DIR/_common.sh"
8
+
9
+ # Guidance
10
+ cat <<EOF
11
+ RECOMMENDED ACTIONS:
12
+ 1. Set dmPolicy to 'pairing' for affected channels:
13
+ openclaw config set channels.whatsapp.dmPolicy pairing
14
+ openclaw config set channels.telegram.dmPolicy pairing
15
+ openclaw config set channels.discord.dmPolicy pairing
16
+ openclaw config set channels.slack.dmPolicy pairing
17
+ openclaw config set channels.signal.dmPolicy pairing
18
+
19
+ 2. Remove wildcard allowFrom if present:
20
+ openclaw config set channels.<channel>.allowFrom '["your-user-id"]'
21
+
22
+ 3. Restart OpenClaw to apply changes:
23
+ openclaw restart
24
+
25
+ EOF
26
+
27
+ exit 0
28
+
@@ -0,0 +1,27 @@
1
+ #!/bin/bash
2
+ # CHECK 2 (new / AC-002): Open group policy
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. Set groupPolicy to 'allowlist' for affected channels:
12
+ openclaw config set channels.whatsapp.groupPolicy allowlist
13
+ openclaw config set channels.telegram.groupPolicy allowlist
14
+ openclaw config set channels.discord.groupPolicy allowlist
15
+ openclaw config set channels.slack.groupPolicy allowlist
16
+ openclaw config set channels.signal.groupPolicy allowlist
17
+
18
+ 2. Define which users are allowed to interact with the Agent:
19
+ openclaw config set channels.<channel>.allowlist '["user-id-1","user-id-2"]'
20
+
21
+ 3. Restart OpenClaw to apply changes:
22
+ openclaw restart
23
+
24
+ EOF
25
+
26
+ exit 0
27
+
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+ # CHECK 3 (new / AC-003): Wildcard allowlist
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. Replace wildcard with specific user IDs for affected channels:
12
+ openclaw config set channels.<channel>.allowlist '["user-id-1","user-id-2"]'
13
+
14
+ 2. Verify current allowlist values:
15
+ openclaw config get channels.<channel>.allowlist
16
+
17
+ 3. Restart OpenClaw to apply changes:
18
+ openclaw restart
19
+
20
+ EOF
21
+
22
+ exit 0
23
+
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+ # CHECK 4 (new / AC-004): No pairing and no allowlist
3
+ # Usage: ./check_4.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
+ Option A - Enable pairing mode (recommended):
12
+ openclaw config set channels.<channel>.dmPolicy pairing
13
+
14
+ Option B - Configure an explicit allowlist:
15
+ openclaw config set channels.<channel>.allowlist '["your-user-id"]'
16
+
17
+ After applying either option, restart OpenClaw:
18
+ openclaw restart
19
+
20
+ EOF
21
+
22
+ exit 0
23
+
@@ -0,0 +1,20 @@
1
+ #!/bin/bash
2
+ # CHECK 5 (new / AC-005): Session DM scope isolation
3
+ # Usage: ./check_5.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. Enable per-channel-peer session isolation:
12
+ openclaw config set session.dmScope per-channel-peer
13
+
14
+ 2. Restart OpenClaw to apply changes:
15
+ openclaw restart
16
+
17
+ EOF
18
+
19
+ exit 0
20
+
@@ -0,0 +1,277 @@
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
+ CONFIG_FILE="$OPENCLAW_DIR/openclaw.json"
19
+ CRED_DIR="$OPENCLAW_DIR/credentials"
20
+ # LOG_DIR="$OPENCLAW_DIR/logs"
21
+ LOG_DIR="logs"
22
+ LOG_FILE="$LOG_DIR/security-scan.log"
23
+ TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
24
+
25
+ export PATH="$HOME/.local/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
26
+
27
+ # Overall counters
28
+ CRITICAL=0
29
+ WARNINGS=0
30
+ CLEAN=0
31
+
32
+ # Per-category counters
33
+ CATEGORY_NAME=""
34
+ CATEGORY_TOTAL_CHECKS=0
35
+ CATEGORY_CRITICAL=0
36
+ CATEGORY_WARNINGS=0
37
+ CATEGORY_CLEAN=0
38
+
39
+ mkdir -p "$LOG_DIR"
40
+
41
+ log() {
42
+ echo "$1" | tee -a "$LOG_FILE"
43
+ }
44
+
45
+ header() {
46
+ # Usage: header <new_index> <message>
47
+ log ""
48
+ log "[$1/$CATEGORY_TOTAL_CHECKS] $2"
49
+ }
50
+
51
+ result_clean() {
52
+ log "CLEAN: $1"
53
+ CLEAN=$((CLEAN + 1))
54
+ CATEGORY_CLEAN=$((CATEGORY_CLEAN + 1))
55
+ }
56
+
57
+ result_warn() {
58
+ log "WARNING: $1"
59
+ WARNINGS=$((WARNINGS + 1))
60
+ CATEGORY_WARNINGS=$((CATEGORY_WARNINGS + 1))
61
+ }
62
+
63
+ result_critical() {
64
+ log "CRITICAL: $1"
65
+ CRITICAL=$((CRITICAL + 1))
66
+ CATEGORY_CRITICAL=$((CATEGORY_CRITICAL + 1))
67
+ }
68
+
69
+ category_start() {
70
+ # Usage: category_start <name> <total_checks>
71
+ CATEGORY_NAME="$1"
72
+ CATEGORY_TOTAL_CHECKS="$2"
73
+ CATEGORY_CRITICAL=0
74
+ CATEGORY_WARNINGS=0
75
+ CATEGORY_CLEAN=0
76
+
77
+ log ""
78
+ log "----------------------------------------"
79
+ log "CATEGORY START: $CATEGORY_NAME ($CATEGORY_TOTAL_CHECKS checks)"
80
+ log "----------------------------------------"
81
+ }
82
+
83
+ category_end() {
84
+ log ""
85
+ log "CATEGORY SUMMARY: $CATEGORY_NAME"
86
+ log " critical: $CATEGORY_CRITICAL"
87
+ log " warning : $CATEGORY_WARNINGS"
88
+ log " clean : $CATEGORY_CLEAN"
89
+ log "----------------------------------------"
90
+ }
91
+
92
+ # Use timeout if available (macOS may only have gtimeout via coreutils)
93
+ TIMEOUT_BIN=""
94
+ if command -v timeout >/dev/null 2>&1; then
95
+ TIMEOUT_BIN="timeout"
96
+ elif command -v gtimeout >/dev/null 2>&1; then
97
+ TIMEOUT_BIN="gtimeout"
98
+ fi
99
+
100
+ run_with_timeout() {
101
+ local secs="$1"
102
+ shift
103
+
104
+ if [ -n "$TIMEOUT_BIN" ]; then
105
+ "$TIMEOUT_BIN" "$secs" "$@"
106
+ elif command -v python3 >/dev/null 2>&1; then
107
+ python3 - "$secs" "$@" <<'PY'
108
+ import subprocess
109
+ import sys
110
+
111
+ def main():
112
+ try:
113
+ secs = float(sys.argv[1])
114
+ except Exception:
115
+ secs = 0
116
+
117
+ cmd = sys.argv[2:]
118
+ if not cmd:
119
+ sys.exit(1)
120
+
121
+ try:
122
+ proc = subprocess.Popen(cmd)
123
+ try:
124
+ proc.wait(timeout=secs if secs > 0 else None)
125
+ except subprocess.TimeoutExpired:
126
+ proc.kill()
127
+ proc.wait()
128
+ sys.exit(124)
129
+ sys.exit(proc.returncode if proc.returncode is not None else 0)
130
+ except FileNotFoundError:
131
+ sys.exit(127)
132
+
133
+ if __name__ == "__main__":
134
+ main()
135
+ PY
136
+ else
137
+ "$@"
138
+ fi
139
+ }
140
+
141
+ # Load IOC data
142
+ load_ips() {
143
+ if [ -f "$IOC_DIR/c2-ips.txt" ]; then
144
+ grep -v '^#' "$IOC_DIR/c2-ips.txt" | grep -v '^$' | cut -d'|' -f1
145
+ else
146
+ echo "91.92.242"
147
+ fi
148
+ }
149
+
150
+ load_domains() {
151
+ if [ -f "$IOC_DIR/malicious-domains.txt" ]; then
152
+ grep -v '^#' "$IOC_DIR/malicious-domains.txt" | grep -v '^$' | cut -d'|' -f1
153
+ else
154
+ echo "webhook.site"
155
+ fi
156
+ }
157
+
158
+ sha256_file() {
159
+ # Cross-platform SHA-256 for a file
160
+ local target="$1"
161
+ [ -f "$target" ] || return 1
162
+
163
+ if command -v sha256sum >/dev/null 2>&1; then
164
+ sha256sum "$target" 2>/dev/null | awk '{print tolower($1)}'
165
+ elif command -v shasum >/dev/null 2>&1; then
166
+ shasum -a 256 "$target" 2>/dev/null | awk '{print tolower($1)}'
167
+ elif command -v openssl >/dev/null 2>&1; then
168
+ openssl dgst -sha256 "$target" 2>/dev/null | sed -E 's/^.*= //' | tr '[:upper:]' '[:lower:]'
169
+ else
170
+ return 1
171
+ fi
172
+ }
173
+
174
+ load_hash_iocs() {
175
+ # Expected format: <sha256>|<campaign>
176
+ # Similar to c2-ips.txt storage style
177
+ if [ -f "$IOC_DIR/malicious-hashes.txt" ]; then
178
+ grep -v '^#' "$IOC_DIR/malicious-hashes.txt" | grep -v '^$' || true
179
+ fi
180
+ }
181
+
182
+ lookup_malicious_hash_campaign() {
183
+ local needle
184
+ needle="$(printf "%s" "${1:-}" | tr '[:upper:]' '[:lower:]')"
185
+ [ -n "$needle" ] || return 1
186
+
187
+ while IFS='|' read -r hash_val campaign rest; do
188
+ hash_val="$(printf "%s" "$hash_val" | tr '[:upper:]' '[:lower:]')"
189
+ if [ "$hash_val" = "$needle" ]; then
190
+ printf "%s\n" "${campaign:-unknown}"
191
+ return 0
192
+ fi
193
+ done < <(load_hash_iocs) # ← 进程替换,不产生子shell
194
+
195
+ return 1
196
+ }
197
+
198
+ extract_github_account_age_days() {
199
+ # Best-effort local metadata extraction only.
200
+ # We cannot reliably infer GitHub account age from filesystem alone unless
201
+ # the skill metadata explicitly records it.
202
+ local skill_dir="$1"
203
+ local val=""
204
+
205
+ for meta in "$skill_dir/package.json" "$skill_dir/config.json" "$skill_dir/SKILL.md"; do
206
+ [ -f "$meta" ] || continue
207
+
208
+ # JSON-ish keys
209
+ val="$(grep -iEo '"(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)"[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
210
+ if [ -n "$val" ]; then
211
+ printf "%s\n" "$val"
212
+ return 0
213
+ fi
214
+
215
+ # YAML / markdown frontmatter-ish keys
216
+ val="$(grep -iE '^(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
217
+ if [ -n "$val" ]; then
218
+ printf "%s\n" "$val"
219
+ return 0
220
+ fi
221
+ done
222
+
223
+ return 1
224
+ }
225
+
226
+
227
+ get_perm() {
228
+ local target="$1"
229
+
230
+ [ -e "$target" ] || {
231
+ echo "unknown"
232
+ return
233
+ }
234
+
235
+ case "$(uname -s)" in
236
+ Darwin|FreeBSD|OpenBSD|NetBSD)
237
+ stat -f '%Lp' "$target" 2>/dev/null || echo "unknown"
238
+ ;;
239
+ Linux)
240
+ stat -c '%a' "$target" 2>/dev/null || echo "unknown"
241
+ ;;
242
+ *)
243
+ stat -c '%a' "$target" 2>/dev/null || stat -f '%Lp' "$target" 2>/dev/null || echo "unknown"
244
+ ;;
245
+ esac
246
+ }
247
+
248
+ has_api_key_patterns() {
249
+ # Return 0 if file contains suspicious API key patterns
250
+ local target="$1"
251
+ [ -f "$target" ] || return 1
252
+
253
+ if grep -E -q \
254
+ 'sk-ant-[A-Za-z0-9_-]{20,}|sk-proj-[A-Za-z0-9_-]{20,}|sk-[A-Za-z0-9_-]{20,}|xoxb-[A-Za-z0-9_-]{20,}|xoxp-[A-Za-z0-9_-]{20,}' \
255
+ "$target" 2>/dev/null; then
256
+ return 0
257
+ fi
258
+ return 1
259
+ }
260
+
261
+ list_memory_files() {
262
+ # Enumerate workspace-level and agent-level memory files
263
+ local f
264
+ for f in \
265
+ "$WORKSPACE_DIR/SOUL.md" \
266
+ "$WORKSPACE_DIR/soul.md" \
267
+ "$WORKSPACE_DIR/MEMORY.md"
268
+ do
269
+ [ -f "$f" ] && echo "$f"
270
+ done
271
+
272
+ if [ -d "$OPENCLAW_DIR/agents" ]; then
273
+ find "$OPENCLAW_DIR/agents" -mindepth 2 -maxdepth 2 -type f \
274
+ \( -name 'soul.md' -o -name 'SOUL.md' -o -name 'MEMORY.md' -o -name 'soul.md' \) \
275
+ 2>/dev/null || true
276
+ fi
277
+ }
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+ # CHECK 1 (origin 21 + CRED-001/002/004/005): Session & Credential Permissions
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ source "$SCRIPT_DIR/_common.sh"
6
+
7
+ # Environment vars (set by caller / _common.sh sourced before this):
8
+ # AUTO_FIX — if "1", automatically remove the offending skill
9
+ # Example: AUTO_FIX=1 ./check_1.sh
10
+
11
+ AGENTS_DIR="$OPENCLAW_DIR/agents"
12
+
13
+ if [ "${AUTO_FIX:-0}" = "1" ]; then
14
+ log "AUTO-FIX: Fixing permissions..."
15
+ [ -d "$OPENCLAW_DIR" ] && chmod 700 "$OPENCLAW_DIR" && log "SUCCESS: chmod 700 $OPENCLAW_DIR"
16
+ [ -f "$CONFIG_FILE" ] && chmod 600 "$CONFIG_FILE" && log "SUCCESS: chmod 600 openclaw.json"
17
+ [ -d "$CRED_DIR" ] && chmod 700 "$CRED_DIR" && log "SUCCESS: chmod 700 credentials/"
18
+ if [ -d "$CRED_DIR" ]; then
19
+ find "$CRED_DIR" -type f -name "*.json" -exec chmod 600 {} \; && log "SUCCESS: chmod 600 credentials/*.json"
20
+ fi
21
+ if [ -d "$AGENTS_DIR" ]; then
22
+ find "$AGENTS_DIR" -mindepth 2 -maxdepth 2 -type d -name "sessions" -exec chmod 700 {} \;
23
+ find "$AGENTS_DIR" -mindepth 3 -maxdepth 3 -type f -exec chmod 600 {} \;
24
+ find "$AGENTS_DIR" -name "auth-profiles.json" -exec chmod 600 {} \;
25
+ log "SUCCESS: Fixed agent session and auth-profile permissions"
26
+ fi
27
+ exit 0
28
+ fi
29
+
30
+ # Guidance
31
+ cat <<EOF
32
+ RECOMMENDED ACTIONS:
33
+ 1. Fix OpenClaw home directory:
34
+ chmod 700 $OPENCLAW_DIR
35
+
36
+ 2. Fix main config file:
37
+ chmod 600 $OPENCLAW_DIR/openclaw.json
38
+
39
+ 3. Fix credentials directory and files:
40
+ chmod 700 $OPENCLAW_DIR/credentials
41
+ chmod 600 $OPENCLAW_DIR/credentials/*.json
42
+
43
+ auto-fix
44
+ EOF
45
+
46
+ exit 0
47
+