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.
- package/README.md +89 -0
- package/bin/whitzard-tui.js +73 -0
- package/bin/whitzard-webui.js +67 -0
- package/dist/tui/tui.js +38733 -0
- package/dist/webui/index.html +1235 -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 +50 -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 +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
|
+
|