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,35 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 2 (new / CRED-007): API keys in memory files
|
|
3
|
+
# Usage: ./check_2.sh
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "$SCRIPT_DIR/_common.sh"
|
|
7
|
+
|
|
8
|
+
FOUND_FILES=()
|
|
9
|
+
AGENTS_DIR="$OPENCLAW_DIR/workspace"
|
|
10
|
+
|
|
11
|
+
while IFS= read -r MEM_FILE; do
|
|
12
|
+
[ -z "$MEM_FILE" ] && continue
|
|
13
|
+
[ -f "$MEM_FILE" ] || continue
|
|
14
|
+
|
|
15
|
+
if has_api_key_patterns "$MEM_FILE"; then
|
|
16
|
+
FOUND_FILES+=("$MEM_FILE")
|
|
17
|
+
fi
|
|
18
|
+
done < <(find "$AGENTS_DIR" -type f \( -name "soul.md" -o -name "SOUL.md" -o -name "MEMORY.md" \) 2>/dev/null)
|
|
19
|
+
|
|
20
|
+
FILE_LIST=""
|
|
21
|
+
for f in "${FOUND_FILES[@]}"; do
|
|
22
|
+
FILE_LIST="${FILE_LIST} $f\n"
|
|
23
|
+
done
|
|
24
|
+
|
|
25
|
+
# Guidance
|
|
26
|
+
cat <<EOF
|
|
27
|
+
RECOMMENDED ACTIONS:
|
|
28
|
+
1. Immediately remove API keys from the flagged memory files:
|
|
29
|
+
$(printf "%b" "$FILE_LIST")
|
|
30
|
+
|
|
31
|
+
2. Review Agent memory for other sensitive data that should not be in context.
|
|
32
|
+
|
|
33
|
+
EOF
|
|
34
|
+
|
|
35
|
+
exit 0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 3 (new / CRED-008): Scan state dir .md/.json files for API keys
|
|
3
|
+
# Usage: ./check_3.sh
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "$SCRIPT_DIR/_common.sh"
|
|
7
|
+
|
|
8
|
+
STATEKEY_ISSUES=0
|
|
9
|
+
FOUND_FILES=()
|
|
10
|
+
|
|
11
|
+
if [ -d "$OPENCLAW_DIR" ]; then
|
|
12
|
+
while IFS= read -r scan_file; do
|
|
13
|
+
[ -z "$scan_file" ] && continue
|
|
14
|
+
|
|
15
|
+
case "$scan_file" in
|
|
16
|
+
*.env)
|
|
17
|
+
continue
|
|
18
|
+
;;
|
|
19
|
+
*/soul.md|*/MEMORY.md|*/SOUL.md)
|
|
20
|
+
continue
|
|
21
|
+
;;
|
|
22
|
+
esac
|
|
23
|
+
|
|
24
|
+
if has_api_key_patterns "$scan_file"; then
|
|
25
|
+
STATEKEY_ISSUES=$((STATEKEY_ISSUES + 1))
|
|
26
|
+
FOUND_FILES+=("$scan_file")
|
|
27
|
+
fi
|
|
28
|
+
done < <(find "$OPENCLAW_DIR" \( -name "*.md" -o -name "*.json" \) -type f 2>/dev/null)
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
FILE_LIST=""
|
|
32
|
+
for f in "${FOUND_FILES[@]}"; do
|
|
33
|
+
FILE_LIST="${FILE_LIST} $f\n"
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
# Guidance
|
|
37
|
+
cat <<EOF
|
|
38
|
+
RECOMMENDED ACTIONS:
|
|
39
|
+
1. Remove API keys from the flagged files:
|
|
40
|
+
$(printf "%b" "$FILE_LIST")
|
|
41
|
+
|
|
42
|
+
2. Store API keys only in the designated credentials directory:
|
|
43
|
+
chmod 700 ~/.openclaw/credentials
|
|
44
|
+
chmod 600 ~/.openclaw/credentials/*.json
|
|
45
|
+
|
|
46
|
+
3. Rotate any exposed API keys from their respective provider dashboards:
|
|
47
|
+
- OpenAI: https://platform.openai.com/api-keys
|
|
48
|
+
- Anthropic: https://console.anthropic.com/settings/keys
|
|
49
|
+
- Google: https://console.cloud.google.com/apis/credentials
|
|
50
|
+
|
|
51
|
+
EOF
|
|
52
|
+
|
|
53
|
+
exit 0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
AUTO-FIX: Fixing permissions...
|
|
2
|
+
SUCCESS: chmod 700 /home/janx/test/openclaw-djy
|
|
3
|
+
AUTO-FIX: Fixing permissions...
|
|
4
|
+
SUCCESS: chmod 700 /home/janx/test/openclaw-djy
|
|
5
|
+
SUCCESS: chmod 600 openclaw.json
|
|
6
|
+
AUTO-FIX: Fixing permissions...
|
|
7
|
+
SUCCESS: chmod 700 /home/janx/test/openclaw-djy
|
|
8
|
+
SUCCESS: chmod 600 openclaw.json
|
|
9
|
+
SUCCESS: Fixed agent session and auth-profile permissions
|
|
10
|
+
WARNING: API key pattern found in 'TOOLS.md'
|
|
11
|
+
Evidence: /home/janx/test/openclaw-djy/workspace/TOOLS.md
|
|
12
|
+
WARNING: API key pattern found in 'USER.md'
|
|
13
|
+
Evidence: /home/janx/test/openclaw-djy/workspace/USER.md
|
|
14
|
+
WARNING: API key pattern found in 'SKILL.md'
|
|
15
|
+
Evidence: /home/janx/test/openclaw-djy/extensions/qqbot copy/skills/qqbot-media/SKILL.md
|
|
@@ -0,0 +1,302 @@
|
|
|
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_JSON="$OPENCLAW_DIR/openclaw.json"
|
|
19
|
+
# LOG_DIR="$OPENCLAW_DIR/logs"
|
|
20
|
+
LOG_DIR="logs"
|
|
21
|
+
LOG_FILE="$LOG_DIR/security-scan.log"
|
|
22
|
+
TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
23
|
+
|
|
24
|
+
export PATH="$HOME/.local/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
25
|
+
|
|
26
|
+
# Overall counters
|
|
27
|
+
CRITICAL=0
|
|
28
|
+
WARNINGS=0
|
|
29
|
+
CLEAN=0
|
|
30
|
+
|
|
31
|
+
# Per-category counters
|
|
32
|
+
CATEGORY_NAME=""
|
|
33
|
+
CATEGORY_TOTAL_CHECKS=0
|
|
34
|
+
CATEGORY_CRITICAL=0
|
|
35
|
+
CATEGORY_WARNINGS=0
|
|
36
|
+
CATEGORY_CLEAN=0
|
|
37
|
+
|
|
38
|
+
mkdir -p "$LOG_DIR"
|
|
39
|
+
|
|
40
|
+
log() {
|
|
41
|
+
echo "$1" | tee -a "$LOG_FILE"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
header() {
|
|
45
|
+
# Usage: header <new_index> <message>
|
|
46
|
+
log ""
|
|
47
|
+
log "[$1/$CATEGORY_TOTAL_CHECKS] $2"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
result_clean() {
|
|
51
|
+
log "CLEAN: $1"
|
|
52
|
+
CLEAN=$((CLEAN + 1))
|
|
53
|
+
CATEGORY_CLEAN=$((CATEGORY_CLEAN + 1))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
result_warn() {
|
|
57
|
+
log "WARNING: $1"
|
|
58
|
+
WARNINGS=$((WARNINGS + 1))
|
|
59
|
+
CATEGORY_WARNINGS=$((CATEGORY_WARNINGS + 1))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
result_critical() {
|
|
63
|
+
log "CRITICAL: $1"
|
|
64
|
+
CRITICAL=$((CRITICAL + 1))
|
|
65
|
+
CATEGORY_CRITICAL=$((CATEGORY_CRITICAL + 1))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
category_start() {
|
|
69
|
+
# Usage: category_start <name> <total_checks>
|
|
70
|
+
CATEGORY_NAME="$1"
|
|
71
|
+
CATEGORY_TOTAL_CHECKS="$2"
|
|
72
|
+
CATEGORY_CRITICAL=0
|
|
73
|
+
CATEGORY_WARNINGS=0
|
|
74
|
+
CATEGORY_CLEAN=0
|
|
75
|
+
|
|
76
|
+
log ""
|
|
77
|
+
log "----------------------------------------"
|
|
78
|
+
log "CATEGORY START: $CATEGORY_NAME ($CATEGORY_TOTAL_CHECKS checks)"
|
|
79
|
+
log "----------------------------------------"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
category_end() {
|
|
83
|
+
log ""
|
|
84
|
+
log "CATEGORY SUMMARY: $CATEGORY_NAME"
|
|
85
|
+
log " critical: $CATEGORY_CRITICAL"
|
|
86
|
+
log " warning : $CATEGORY_WARNINGS"
|
|
87
|
+
log " clean : $CATEGORY_CLEAN"
|
|
88
|
+
log "----------------------------------------"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Use timeout if available (macOS may only have gtimeout via coreutils)
|
|
92
|
+
TIMEOUT_BIN=""
|
|
93
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
94
|
+
TIMEOUT_BIN="timeout"
|
|
95
|
+
elif command -v gtimeout >/dev/null 2>&1; then
|
|
96
|
+
TIMEOUT_BIN="gtimeout"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
run_with_timeout() {
|
|
100
|
+
local secs="$1"
|
|
101
|
+
shift
|
|
102
|
+
|
|
103
|
+
if [ -n "$TIMEOUT_BIN" ]; then
|
|
104
|
+
"$TIMEOUT_BIN" "$secs" "$@"
|
|
105
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
106
|
+
python3 - "$secs" "$@" <<'PY'
|
|
107
|
+
import subprocess
|
|
108
|
+
import sys
|
|
109
|
+
|
|
110
|
+
def main():
|
|
111
|
+
try:
|
|
112
|
+
secs = float(sys.argv[1])
|
|
113
|
+
except Exception:
|
|
114
|
+
secs = 0
|
|
115
|
+
|
|
116
|
+
cmd = sys.argv[2:]
|
|
117
|
+
if not cmd:
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
proc = subprocess.Popen(cmd)
|
|
122
|
+
try:
|
|
123
|
+
proc.wait(timeout=secs if secs > 0 else None)
|
|
124
|
+
except subprocess.TimeoutExpired:
|
|
125
|
+
proc.kill()
|
|
126
|
+
proc.wait()
|
|
127
|
+
sys.exit(124)
|
|
128
|
+
sys.exit(proc.returncode if proc.returncode is not None else 0)
|
|
129
|
+
except FileNotFoundError:
|
|
130
|
+
sys.exit(127)
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
main()
|
|
134
|
+
PY
|
|
135
|
+
else
|
|
136
|
+
"$@"
|
|
137
|
+
fi
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Load IOC data
|
|
141
|
+
load_ips() {
|
|
142
|
+
if [ -f "$IOC_DIR/c2-ips.txt" ]; then
|
|
143
|
+
grep -v '^#' "$IOC_DIR/c2-ips.txt" | grep -v '^$' | cut -d'|' -f1
|
|
144
|
+
else
|
|
145
|
+
echo "91.92.242"
|
|
146
|
+
fi
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
load_domains() {
|
|
150
|
+
if [ -f "$IOC_DIR/malicious-domains.txt" ]; then
|
|
151
|
+
grep -v '^#' "$IOC_DIR/malicious-domains.txt" | grep -v '^$' | cut -d'|' -f1
|
|
152
|
+
else
|
|
153
|
+
echo "webhook.site"
|
|
154
|
+
fi
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
sha256_file() {
|
|
158
|
+
# Cross-platform SHA-256 for a file
|
|
159
|
+
local target="$1"
|
|
160
|
+
[ -f "$target" ] || return 1
|
|
161
|
+
|
|
162
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
163
|
+
sha256sum "$target" 2>/dev/null | awk '{print tolower($1)}'
|
|
164
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
165
|
+
shasum -a 256 "$target" 2>/dev/null | awk '{print tolower($1)}'
|
|
166
|
+
elif command -v openssl >/dev/null 2>&1; then
|
|
167
|
+
openssl dgst -sha256 "$target" 2>/dev/null | sed -E 's/^.*= //' | tr '[:upper:]' '[:lower:]'
|
|
168
|
+
else
|
|
169
|
+
return 1
|
|
170
|
+
fi
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
load_hash_iocs() {
|
|
174
|
+
# Expected format: <sha256>|<campaign>
|
|
175
|
+
# Similar to c2-ips.txt storage style
|
|
176
|
+
if [ -f "$IOC_DIR/malicious-hashes.txt" ]; then
|
|
177
|
+
grep -v '^#' "$IOC_DIR/malicious-hashes.txt" | grep -v '^$' || true
|
|
178
|
+
fi
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
lookup_malicious_hash_campaign() {
|
|
182
|
+
# Usage: lookup_malicious_hash_campaign <sha256>
|
|
183
|
+
local needle
|
|
184
|
+
needle="$(printf "%s" "${1:-}" | tr '[:upper:]' '[:lower:]')"
|
|
185
|
+
[ -n "$needle" ] || return 1
|
|
186
|
+
|
|
187
|
+
load_hash_iocs | 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
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
extract_github_account_age_days() {
|
|
197
|
+
# Best-effort local metadata extraction only.
|
|
198
|
+
# We cannot reliably infer GitHub account age from filesystem alone unless
|
|
199
|
+
# the skill metadata explicitly records it.
|
|
200
|
+
local skill_dir="$1"
|
|
201
|
+
local val=""
|
|
202
|
+
|
|
203
|
+
for meta in "$skill_dir/package.json" "$skill_dir/config.json" "$skill_dir/SKILL.md"; do
|
|
204
|
+
[ -f "$meta" ] || continue
|
|
205
|
+
|
|
206
|
+
# JSON-ish keys
|
|
207
|
+
val="$(grep -iEo '"(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)"[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
|
|
208
|
+
if [ -n "$val" ]; then
|
|
209
|
+
printf "%s\n" "$val"
|
|
210
|
+
return 0
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# YAML / markdown frontmatter-ish keys
|
|
214
|
+
val="$(grep -iE '^(githubAccountAge|github_account_age|accountAgeDays|githubAccountAgeDays)[[:space:]]*:[[:space:]]*[0-9]+' "$meta" 2>/dev/null | head -1 | grep -oE '[0-9]+' || true)"
|
|
215
|
+
if [ -n "$val" ]; then
|
|
216
|
+
printf "%s\n" "$val"
|
|
217
|
+
return 0
|
|
218
|
+
fi
|
|
219
|
+
done
|
|
220
|
+
|
|
221
|
+
return 1
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
OPENCLAW_PRESENT=false
|
|
225
|
+
if command -v openclaw >/dev/null 2>&1; then
|
|
226
|
+
OPENCLAW_PRESENT=true
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
OC_VERSION="unknown"
|
|
230
|
+
PARSED_OC_VERSION=""
|
|
231
|
+
OC_MAJOR=""
|
|
232
|
+
OC_MINOR=""
|
|
233
|
+
OC_PATCH=""
|
|
234
|
+
|
|
235
|
+
if [ "$OPENCLAW_PRESENT" = true ]; then
|
|
236
|
+
OC_VERSION="$(run_with_timeout 5 openclaw --version 2>/dev/null || echo "unknown")"
|
|
237
|
+
PARSED_OC_VERSION="$(echo "$OC_VERSION" | grep -oE '[0-9]{4}\.[0-9]+\.[0-9]+' | head -1 || true)"
|
|
238
|
+
if [ -n "$PARSED_OC_VERSION" ]; then
|
|
239
|
+
OC_MAJOR="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f1)"
|
|
240
|
+
OC_MINOR="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f2)"
|
|
241
|
+
OC_PATCH="$(echo "$PARSED_OC_VERSION" | cut -d'.' -f3)"
|
|
242
|
+
fi
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
get_oc_config() {
|
|
246
|
+
# Usage: get_oc_config <key> <default>
|
|
247
|
+
local key="$1"
|
|
248
|
+
local default_val="${2:-}"
|
|
249
|
+
if [ "$OPENCLAW_PRESENT" = true ]; then
|
|
250
|
+
run_with_timeout 10 openclaw config get "$key" 2>/dev/null || echo "$default_val"
|
|
251
|
+
else
|
|
252
|
+
echo "$default_val"
|
|
253
|
+
fi
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
version_ge() {
|
|
257
|
+
# Usage: version_ge <major> <minor> <patch>
|
|
258
|
+
local t_major="$1"
|
|
259
|
+
local t_minor="$2"
|
|
260
|
+
local t_patch="$3"
|
|
261
|
+
|
|
262
|
+
if [ -z "$PARSED_OC_VERSION" ]; then
|
|
263
|
+
return 1
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
if [ "$OC_MAJOR" -gt "$t_major" ] 2>/dev/null; then
|
|
267
|
+
return 0
|
|
268
|
+
fi
|
|
269
|
+
if [ "$OC_MAJOR" -lt "$t_major" ] 2>/dev/null; then
|
|
270
|
+
return 1
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
if [ "$OC_MINOR" -gt "$t_minor" ] 2>/dev/null; then
|
|
274
|
+
return 0
|
|
275
|
+
fi
|
|
276
|
+
if [ "$OC_MINOR" -lt "$t_minor" ] 2>/dev/null; then
|
|
277
|
+
return 1
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
if [ "$OC_PATCH" -ge "$t_patch" ] 2>/dev/null; then
|
|
281
|
+
return 0
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
return 1
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
version_lt() {
|
|
288
|
+
# Usage: version_lt <major> <minor> <patch>
|
|
289
|
+
if [ -z "$PARSED_OC_VERSION" ]; then
|
|
290
|
+
return 1
|
|
291
|
+
fi
|
|
292
|
+
if version_ge "$1" "$2" "$3"; then
|
|
293
|
+
return 1
|
|
294
|
+
fi
|
|
295
|
+
return 0
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
get_perm() {
|
|
299
|
+
# Cross-platform numeric permissions
|
|
300
|
+
local target="$1"
|
|
301
|
+
stat -f "%Lp" "$target" 2>/dev/null || stat -c "%a" "$target" 2>/dev/null || echo "unknown"
|
|
302
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 1 (origin 18): Tool Policy / Elevated Tools Audit
|
|
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
|
+
# Auto-fix
|
|
12
|
+
if [ "${AUTO_FIX:-0}" = "1" ]; then
|
|
13
|
+
CONFIG_JSON="$OPENCLAW_DIR/openclaw.json"
|
|
14
|
+
|
|
15
|
+
python3 <<EOF
|
|
16
|
+
import json, sys
|
|
17
|
+
|
|
18
|
+
config_path = "$CONFIG_JSON"
|
|
19
|
+
|
|
20
|
+
with open(config_path) as f:
|
|
21
|
+
data = json.load(f)
|
|
22
|
+
|
|
23
|
+
tools = data.setdefault("tools", {})
|
|
24
|
+
elevated = tools.setdefault("elevated", {})
|
|
25
|
+
|
|
26
|
+
# 1 限制 allowFrom
|
|
27
|
+
elevated["allowFrom"] = {"owner": True}
|
|
28
|
+
|
|
29
|
+
# 2 启用审批
|
|
30
|
+
elevated["requireApproval"] = True
|
|
31
|
+
|
|
32
|
+
# 3 添加 deny list
|
|
33
|
+
tools["deny"] = [
|
|
34
|
+
"exec",
|
|
35
|
+
"process",
|
|
36
|
+
"browser",
|
|
37
|
+
"filesystem-write"
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
with open(config_path, "w") as f:
|
|
41
|
+
json.dump(data, f, indent=2)
|
|
42
|
+
|
|
43
|
+
print("SUCCESS: Fixed tool policy security configuration.")
|
|
44
|
+
print("Attention: You need to manually set 'tools.elevated.allowFrom' which contains the allow list for each provider (e.g., Discord, WhatsApp). Owner is just an example.")
|
|
45
|
+
EOF
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Guidance
|
|
50
|
+
cat << EOF
|
|
51
|
+
RECOMMENDED ACTIONS:
|
|
52
|
+
1. Restrict elevated tools to specific principals
|
|
53
|
+
openclaw config set tools.elevated.allowFrom '["your-user-id"]'
|
|
54
|
+
|
|
55
|
+
2. Require approval for elevated actions
|
|
56
|
+
openclaw config set tools.elevated.requireApproval true
|
|
57
|
+
|
|
58
|
+
3. Add dangerous tools to the deny list (optional but recommend)
|
|
59
|
+
openclaw config set tools.deny '["exec","process","browser","filesystem-write"]'
|
|
60
|
+
|
|
61
|
+
4. Review current tool permissions
|
|
62
|
+
openclaw config get tools
|
|
63
|
+
|
|
64
|
+
auto-fix
|
|
65
|
+
EOF
|
|
66
|
+
|
|
67
|
+
exit 0
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 10 (origin 19): Sandbox Configuration
|
|
3
|
+
# Usage: ./check_10.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 sandboxing for all skills:
|
|
12
|
+
openclaw config set sandbox.mode all
|
|
13
|
+
|
|
14
|
+
2. Restart OpenClaw to apply changes:
|
|
15
|
+
openclaw restart
|
|
16
|
+
|
|
17
|
+
3. Verify sandbox is active:
|
|
18
|
+
openclaw config get sandbox.mode
|
|
19
|
+
# Should return: all
|
|
20
|
+
EOF
|
|
21
|
+
|
|
22
|
+
exit 0
|
|
23
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 11 (origin 28): Node.js Version / Known CVEs
|
|
3
|
+
# Usage: ./check_11.sh
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "$SCRIPT_DIR/_common.sh"
|
|
7
|
+
|
|
8
|
+
NODE_VERSION="$(node --version 2>/dev/null)"
|
|
9
|
+
|
|
10
|
+
# Guidance
|
|
11
|
+
cat <<EOF
|
|
12
|
+
RECOMMENDED ACTIONS:
|
|
13
|
+
1. Upgrade Node.js to 22.12.0 or later using nvm (recommended):
|
|
14
|
+
nvm install 22
|
|
15
|
+
nvm use 22
|
|
16
|
+
nvm alias default 22
|
|
17
|
+
|
|
18
|
+
2. Or upgrade via package manager (Ubuntu/Debian):
|
|
19
|
+
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
|
20
|
+
sudo apt-get install -y nodejs
|
|
21
|
+
|
|
22
|
+
3. Verify the upgrade:
|
|
23
|
+
node --version
|
|
24
|
+
# Should show v22.12.0 or later
|
|
25
|
+
|
|
26
|
+
4. Restart OpenClaw after upgrade:
|
|
27
|
+
openclaw restart
|
|
28
|
+
|
|
29
|
+
Reference: https://nodejs.org/en/blog/vulnerability/
|
|
30
|
+
|
|
31
|
+
EOF
|
|
32
|
+
|
|
33
|
+
exit 0
|
|
34
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 12 (origin 24): Log Redaction Audit
|
|
3
|
+
# Usage: ./check_12.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 log redaction
|
|
12
|
+
openclaw config set logging.redactSensitive true
|
|
13
|
+
|
|
14
|
+
2. Fix log directory permissions
|
|
15
|
+
chmod 700 ~/.openclaw/logs
|
|
16
|
+
chmod 700 /tmp/openclaw 2>/dev/null
|
|
17
|
+
|
|
18
|
+
3. Verify logs don't contain sensitive data
|
|
19
|
+
grep -iE "sk-[a-zA-Z0-9]{10}|password|token.*=" ~/.openclaw/logs/*.log
|
|
20
|
+
|
|
21
|
+
4. Set up log rotation (macOS - newsyslog)
|
|
22
|
+
sudo sh -c 'echo "/home/*/.openclaw/logs/*.log 640 7 1000 * J" >> /etc/newsyslog.conf'
|
|
23
|
+
|
|
24
|
+
5. Linux - logrotate
|
|
25
|
+
cat > /etc/logrotate.d/openclaw << 'LOGROTATE'
|
|
26
|
+
/home/*/.openclaw/logs/*.log {
|
|
27
|
+
weekly
|
|
28
|
+
rotate 4
|
|
29
|
+
compress
|
|
30
|
+
missingok
|
|
31
|
+
notifempty
|
|
32
|
+
create 0600 root root
|
|
33
|
+
}
|
|
34
|
+
LOGROTATE
|
|
35
|
+
EOF
|
|
36
|
+
|
|
37
|
+
exit 0
|
|
38
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 13 (origin 22): Persistence Mechanism Scan
|
|
3
|
+
# Usage: ./check_13.sh
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "$SCRIPT_DIR/_common.sh"
|
|
7
|
+
|
|
8
|
+
# Guidance
|
|
9
|
+
cat <<EOF
|
|
10
|
+
RECOMMENDED ACTIONS:
|
|
11
|
+
1. Review and remove suspicious crontab entries:
|
|
12
|
+
crontab -l | grep -iE "openclaw|clawdbot|moltbot"
|
|
13
|
+
crontab -e
|
|
14
|
+
|
|
15
|
+
2. Check systemd user services:
|
|
16
|
+
systemctl --user list-units --type=service | grep -iE "openclaw|clawdbot|moltbot"
|
|
17
|
+
|
|
18
|
+
3. Disable unauthorized systemd services:
|
|
19
|
+
systemctl --user disable --now <service-name>
|
|
20
|
+
rm ~/.config/systemd/user/<service-name>.service
|
|
21
|
+
|
|
22
|
+
4. Check system-level services (requires sudo):
|
|
23
|
+
sudo systemctl list-units --type=service | grep -iE "openclaw|clawdbot|moltbot"
|
|
24
|
+
sudo systemctl disable --now <service-name>
|
|
25
|
+
|
|
26
|
+
EOF
|
|
27
|
+
|
|
28
|
+
exit 0
|
|
29
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 2 (origin 26): Exec-Approvals Configuration
|
|
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_2.sh
|
|
10
|
+
|
|
11
|
+
EXEC_FILE="$OPENCLAW_DIR/exec-approvals.json"
|
|
12
|
+
|
|
13
|
+
if [ "${AUTO_FIX:-0}" = "1" ]; then
|
|
14
|
+
if [ -f "$EXEC_FILE" ]; then
|
|
15
|
+
if [ "$EXEC_PERMS" != "600" ] && [ "$EXEC_PERMS" != "unknown" ]; then
|
|
16
|
+
chmod 600 "$EXEC_FILE" && log "SUCCESS: chmod 600 exec-approvals.json" \
|
|
17
|
+
|| log "ERROR: Failed to chmod exec-approvals.json"
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
if [ -n "$UNSAFE_EXEC" ]; then
|
|
21
|
+
log "WARNING: Unsafe exec-approvals config requires manual review - cannot auto-fix safely"
|
|
22
|
+
fi
|
|
23
|
+
fi
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Guidance
|
|
28
|
+
cat <<EOF
|
|
29
|
+
RECOMMENDED ACTIONS:
|
|
30
|
+
1. Review and fix exec-approvals.json manually:
|
|
31
|
+
cat $EXEC_FILE
|
|
32
|
+
|
|
33
|
+
2. Remove any entries with "security": "allow" or "ask": "off":
|
|
34
|
+
- These settings bypass confirmation prompts entirely
|
|
35
|
+
- Replace with "ask": "on" or remove the entry
|
|
36
|
+
|
|
37
|
+
3. Fix file permissions:
|
|
38
|
+
chmod 600 $EXEC_FILE
|
|
39
|
+
|
|
40
|
+
4. Restart OpenClaw to apply changes:
|
|
41
|
+
openclaw restart
|
|
42
|
+
|
|
43
|
+
auto-fix
|
|
44
|
+
EOF
|
|
45
|
+
|
|
46
|
+
exit 0
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CHECK 3 (origin 27 + EXEC-005/006/007): Docker Container Security
|
|
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. Run container as non-root user (add to docker-compose.yml or Dockerfile):
|
|
12
|
+
user: "1000:1000"
|
|
13
|
+
|
|
14
|
+
2. Remove Docker socket mount if present:
|
|
15
|
+
# Remove this line from your compose/run config:
|
|
16
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
17
|
+
|
|
18
|
+
3. Disable privileged mode:
|
|
19
|
+
privileged: false
|
|
20
|
+
|
|
21
|
+
4. Drop all Linux capabilities:
|
|
22
|
+
cap_drop:
|
|
23
|
+
- ALL
|
|
24
|
+
|
|
25
|
+
5. Prevent privilege escalation:
|
|
26
|
+
security_opt:
|
|
27
|
+
- no-new-privileges:true
|
|
28
|
+
|
|
29
|
+
6. Use bridge network instead of host:
|
|
30
|
+
network_mode: bridge
|
|
31
|
+
|
|
32
|
+
7. Restart containers after applying changes:
|
|
33
|
+
docker compose down && docker compose up -d
|
|
34
|
+
|
|
35
|
+
EOF
|
|
36
|
+
|
|
37
|
+
exit 0
|