docs(openclaw): move doc to OPEN_CLAW/ dir, add security validation script

- Move openclaw-personal-ai-assistant.md from windows_specific/ to OPEN_CLAW/
- Add validate-security.sh: cross-platform security scanner with visual output
  - 7 check categories: installation, gateway config, file permissions,
    network security, API keys, system security, openclaw doctor
  - Green ticks (pass), red crosses (fail), yellow warnings
  - Numbered recommendations list with fix commands
  - Works on macOS, Linux, and WSL2
This commit is contained in:
saravanakumardb1 2026-02-22 14:39:34 -08:00
parent 4dd8003f25
commit 5667308629
2 changed files with 487 additions and 0 deletions

View File

@ -0,0 +1,487 @@
#!/bin/bash
# =============================================================================
# OpenClaw Security Validator
# =============================================================================
# Run this script AFTER installing OpenClaw to verify your setup is secure.
# Works on both macOS and Linux/WSL2.
#
# Usage:
# bash validate-security.sh
#
# Output:
# ✅ = Secure (green)
# ❌ = Insecure — action required (red)
# ⚠️ = Warning — review recommended (yellow)
# = Info (blue)
# =============================================================================
set -euo pipefail
# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'
# --- Counters ---
PASS=0
FAIL=0
WARN=0
RECOMMENDATIONS=()
# --- Helpers ---
pass() {
echo -e " ${GREEN}$1${NC}"
((PASS++))
}
fail() {
echo -e " ${RED}$1${NC}"
((FAIL++))
RECOMMENDATIONS+=("$2")
}
warn() {
echo -e " ${YELLOW}⚠️ $1${NC}"
((WARN++))
RECOMMENDATIONS+=("$2")
}
info() {
echo -e " ${BLUE} $1${NC}"
}
section() {
echo ""
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BOLD} $1${NC}"
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
# --- Detect OS ---
OS="unknown"
if [[ "$(uname -s)" == "Darwin" ]]; then
OS="macos"
elif grep -qi microsoft /proc/version 2>/dev/null; then
OS="wsl2"
elif [[ "$(uname -s)" == "Linux" ]]; then
OS="linux"
fi
OPENCLAW_DIR="$HOME/.openclaw"
CONFIG_FILE="$OPENCLAW_DIR/config.yaml"
# =============================================================================
echo ""
echo -e "${BOLD}🦞 OpenClaw Security Validator${NC}"
echo -e " $(date '+%Y-%m-%d %H:%M:%S')"
echo -e " Platform: ${BOLD}$OS${NC}"
echo ""
# =============================================================================
section "1. OpenClaw Installation"
# =============================================================================
# Check if openclaw is installed
if command -v openclaw &>/dev/null; then
VERSION=$(openclaw --version 2>/dev/null || echo "unknown")
pass "OpenClaw installed: $VERSION"
else
fail "OpenClaw is NOT installed" \
"Install OpenClaw: npm install -g openclaw@latest && openclaw onboard --install-daemon"
fi
# Check Node.js version
if command -v node &>/dev/null; then
NODE_VER=$(node --version 2>/dev/null | sed 's/v//')
NODE_MAJOR=$(echo "$NODE_VER" | cut -d. -f1)
if [[ "$NODE_MAJOR" -ge 22 ]]; then
pass "Node.js version: v$NODE_VER (>= 22 required)"
else
fail "Node.js version: v$NODE_VER (NEEDS >= 22)" \
"Upgrade Node.js: nvm install 22 && nvm alias default 22"
fi
else
fail "Node.js is NOT installed" \
"Install Node.js 22+: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash && nvm install 22"
fi
# Check config exists
if [[ -f "$CONFIG_FILE" ]]; then
pass "Config file exists: $CONFIG_FILE"
else
fail "Config file NOT found at $CONFIG_FILE" \
"Run: openclaw onboard --install-daemon"
fi
# =============================================================================
section "2. Gateway Configuration"
# =============================================================================
if [[ -f "$CONFIG_FILE" ]]; then
# Check bind address
BIND_ADDR=$(grep -E '^\s*bind:' "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/.*bind:\s*//' | tr -d '"' | tr -d "'" | xargs)
if [[ -z "$BIND_ADDR" ]]; then
warn "Gateway bind address not explicitly set (may default to 0.0.0.0)" \
"Add to config.yaml under gateway: bind: \"127.0.0.1\""
elif [[ "$BIND_ADDR" == "127.0.0.1" || "$BIND_ADDR" == "localhost" ]]; then
pass "Gateway binds to loopback only: $BIND_ADDR"
elif [[ "$BIND_ADDR" == "0.0.0.0" ]]; then
fail "Gateway binds to ALL interfaces (0.0.0.0) — EXPOSED TO NETWORK!" \
"CRITICAL: Change gateway.bind to \"127.0.0.1\" in $CONFIG_FILE immediately"
else
warn "Gateway binds to: $BIND_ADDR — verify this is intentional" \
"Recommended: Set gateway.bind to \"127.0.0.1\" unless you have a specific reason"
fi
# Check auth mode
AUTH_MODE=$(grep -E '^\s*mode:' "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/.*mode:\s*//' | tr -d '"' | tr -d "'" | xargs)
if [[ "$AUTH_MODE" == "password" ]]; then
pass "Gateway auth mode: password"
# Check password strength
PASSWORD=$(grep -E '^\s*password:' "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/.*password:\s*//' | tr -d '"' | tr -d "'" | xargs)
if [[ -z "$PASSWORD" ]]; then
fail "Gateway password is EMPTY" \
"Set a strong password: openclaw config set gateway.auth.password \"\$(openssl rand -base64 32)\""
elif [[ ${#PASSWORD} -lt 16 ]]; then
warn "Gateway password is short (${#PASSWORD} chars, recommend 20+)" \
"Set stronger password: openclaw config set gateway.auth.password \"\$(openssl rand -base64 32)\""
else
pass "Gateway password length: ${#PASSWORD} chars"
fi
elif [[ -z "$AUTH_MODE" ]]; then
fail "Gateway auth mode NOT configured — WebChat/Control UI may be unprotected" \
"Add to config.yaml: gateway.auth.mode: \"password\" and set a strong password"
else
warn "Gateway auth mode: $AUTH_MODE — verify this is secure" \
"Recommended: Set gateway.auth.mode to \"password\""
fi
# Check DM policy
DM_POLICY=$(grep -E '^\s*dmPolicy:' "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/.*dmPolicy:\s*//' | tr -d '"' | tr -d "'" | xargs)
if [[ "$DM_POLICY" == "pairing" ]]; then
pass "DM policy: pairing (unknown senders must be approved)"
elif [[ "$DM_POLICY" == "open" ]]; then
fail "DM policy is OPEN — ANYONE can message your bot!" \
"CRITICAL: Change dmPolicy to \"pairing\" in $CONFIG_FILE immediately"
elif [[ -z "$DM_POLICY" ]]; then
warn "DM policy not explicitly set (check default behavior)" \
"Explicitly set dmPolicy: \"pairing\" in $CONFIG_FILE"
else
info "DM policy: $DM_POLICY"
fi
# Check Tailscale mode
TS_MODE=$(grep -A5 "tailscale:" "$CONFIG_FILE" 2>/dev/null | grep -E '^\s*mode:' | head -1 | sed 's/.*mode:\s*//' | tr -d '"' | tr -d "'" | xargs)
if [[ "$TS_MODE" == "serve" ]]; then
pass "Tailscale mode: serve (tailnet-only, not public)"
elif [[ "$TS_MODE" == "funnel" ]]; then
fail "Tailscale mode is FUNNEL — Gateway is publicly accessible!" \
"Change tailscale.mode to \"serve\" unless you absolutely need public access. If you do, ensure gateway.auth.mode is \"password\" with a strong password."
elif [[ -z "$TS_MODE" || "$TS_MODE" == "off" ]]; then
pass "Tailscale mode: off/unset (no Tailscale exposure)"
fi
# Check for dangerous tools
SYSTEM_RUN=$(grep -A3 "system:" "$CONFIG_FILE" 2>/dev/null | grep -A1 "run:" | grep "enabled:" | sed 's/.*enabled:\s*//' | tr -d ' ' | head -1)
if [[ "$SYSTEM_RUN" == "true" ]]; then
fail "system.run tool is ENABLED — allows arbitrary command execution!" \
"CRITICAL: Disable system.run in config.yaml: tools.system.run.enabled: false"
elif [[ "$SYSTEM_RUN" == "false" ]]; then
pass "system.run tool: disabled"
else
warn "system.run tool status unknown — verify manually" \
"Explicitly set tools.system.run.enabled: false in $CONFIG_FILE"
fi
BROWSER_ENABLED=$(grep -A2 "browser:" "$CONFIG_FILE" 2>/dev/null | grep "enabled:" | sed 's/.*enabled:\s*//' | tr -d ' ' | head -1)
if [[ "$BROWSER_ENABLED" == "true" ]]; then
warn "Browser control is ENABLED — agent can browse authenticated sessions" \
"Disable browser unless needed: tools.browser.enabled: false"
elif [[ "$BROWSER_ENABLED" == "false" ]]; then
pass "Browser control: disabled"
else
warn "Browser control status unknown — verify manually" \
"Explicitly set tools.browser.enabled: false in $CONFIG_FILE"
fi
else
info "Skipping config checks — config file not found"
fi
# =============================================================================
section "3. File Permissions"
# =============================================================================
if [[ -d "$OPENCLAW_DIR" ]]; then
# Check ~/.openclaw directory permissions
DIR_PERMS=$(stat -c "%a" "$OPENCLAW_DIR" 2>/dev/null || stat -f "%Lp" "$OPENCLAW_DIR" 2>/dev/null)
if [[ "$DIR_PERMS" == "700" ]]; then
pass "~/.openclaw/ directory permissions: $DIR_PERMS (owner-only)"
else
fail "~/.openclaw/ directory permissions: $DIR_PERMS (should be 700)" \
"Fix: chmod 700 ~/.openclaw"
fi
# Check config file permissions
if [[ -f "$CONFIG_FILE" ]]; then
FILE_PERMS=$(stat -c "%a" "$CONFIG_FILE" 2>/dev/null || stat -f "%Lp" "$CONFIG_FILE" 2>/dev/null)
if [[ "$FILE_PERMS" == "600" ]]; then
pass "config.yaml permissions: $FILE_PERMS (owner read/write only)"
else
fail "config.yaml permissions: $FILE_PERMS (should be 600 — contains API keys!)" \
"Fix: chmod 600 ~/.openclaw/config.yaml"
fi
fi
# Check WhatsApp session directory
WA_DIR="$OPENCLAW_DIR/whatsapp"
if [[ -d "$WA_DIR" ]]; then
WA_PERMS=$(stat -c "%a" "$WA_DIR" 2>/dev/null || stat -f "%Lp" "$WA_DIR" 2>/dev/null)
if [[ "$WA_PERMS" == "700" ]]; then
pass "WhatsApp session dir permissions: $WA_PERMS"
else
warn "WhatsApp session dir permissions: $WA_PERMS (should be 700)" \
"Fix: chmod -R 700 ~/.openclaw/whatsapp"
fi
fi
else
info "~/.openclaw/ directory not found — skipping permission checks"
fi
# Check running as root
if [[ "$(id -u)" == "0" ]]; then
fail "Running as ROOT — never run OpenClaw as root!" \
"Switch to a regular user: su - yourusername"
else
pass "Not running as root: $(whoami)"
fi
# =============================================================================
section "4. Network Security"
# =============================================================================
# Check if Gateway port is listening
GW_PORT=18789
if [[ -f "$CONFIG_FILE" ]]; then
CUSTOM_PORT=$(grep -E '^\s*port:' "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/.*port:\s*//' | tr -d ' ')
if [[ -n "$CUSTOM_PORT" && "$CUSTOM_PORT" =~ ^[0-9]+$ ]]; then
GW_PORT=$CUSTOM_PORT
fi
fi
if command -v ss &>/dev/null; then
LISTEN_ADDR=$(ss -tlnp 2>/dev/null | grep ":${GW_PORT}" | awk '{print $4}' | head -1)
elif command -v lsof &>/dev/null; then
LISTEN_ADDR=$(lsof -iTCP:${GW_PORT} -sTCP:LISTEN -P -n 2>/dev/null | awk 'NR>1{print $9}' | head -1)
else
LISTEN_ADDR=""
fi
if [[ -n "$LISTEN_ADDR" ]]; then
if echo "$LISTEN_ADDR" | grep -q "127.0.0.1\|localhost\|\[::1\]"; then
pass "Gateway listening on loopback only: $LISTEN_ADDR"
elif echo "$LISTEN_ADDR" | grep -q "0.0.0.0\|\*:\|\[::\]"; then
fail "Gateway listening on ALL interfaces: $LISTEN_ADDR" \
"CRITICAL: Change gateway.bind to \"127.0.0.1\" and restart OpenClaw"
else
warn "Gateway listening on: $LISTEN_ADDR — verify this is intentional" \
"Recommended: Bind to 127.0.0.1 unless you have a specific reason"
fi
else
info "Gateway not currently running on port $GW_PORT (or cannot detect)"
fi
# Check SSH
if [[ "$OS" == "wsl2" || "$OS" == "linux" ]]; then
if systemctl is-active ssh &>/dev/null 2>&1 || systemctl is-active sshd &>/dev/null 2>&1; then
warn "SSH service is RUNNING — disable if not needed" \
"Disable SSH: sudo systemctl disable --now ssh"
else
pass "SSH service: not running"
fi
fi
# Check UFW (Linux/WSL2)
if [[ "$OS" == "wsl2" || "$OS" == "linux" ]]; then
if command -v ufw &>/dev/null; then
UFW_STATUS=$(sudo ufw status 2>/dev/null | head -1 || echo "unknown")
if echo "$UFW_STATUS" | grep -q "active"; then
pass "UFW firewall: active"
else
warn "UFW firewall: inactive" \
"Enable: sudo ufw default deny incoming && sudo ufw allow from 127.0.0.1 to any port $GW_PORT && sudo ufw enable"
fi
else
warn "UFW not installed" \
"Install: sudo apt install -y ufw && sudo ufw default deny incoming && sudo ufw enable"
fi
fi
# Check macOS firewall
if [[ "$OS" == "macos" ]]; then
FW_STATE=$(sudo /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate 2>/dev/null | grep -o "enabled\|disabled" || echo "unknown")
if [[ "$FW_STATE" == "enabled" ]]; then
pass "macOS firewall: enabled"
elif [[ "$FW_STATE" == "disabled" ]]; then
warn "macOS firewall: disabled" \
"Enable: System Settings → Network → Firewall → Turn On"
else
info "Could not check macOS firewall state"
fi
fi
# =============================================================================
section "5. API Key Security"
# =============================================================================
if [[ -f "$CONFIG_FILE" ]]; then
# Check for hardcoded API keys
if grep -qE 'sk-[a-zA-Z0-9]{20,}' "$CONFIG_FILE" 2>/dev/null; then
warn "Hardcoded OpenAI API key found in config.yaml" \
"Prefer OAuth login: openclaw auth login openai (keys in config are readable by anyone with file access)"
else
pass "No hardcoded OpenAI API key in config.yaml"
fi
if grep -qE 'sk-ant-[a-zA-Z0-9]{20,}' "$CONFIG_FILE" 2>/dev/null; then
warn "Hardcoded Anthropic API key found in config.yaml" \
"Prefer OAuth login: openclaw auth login anthropic"
else
pass "No hardcoded Anthropic API key in config.yaml"
fi
# Check if config is in a git repo
if git -C "$OPENCLAW_DIR" rev-parse --is-inside-work-tree &>/dev/null; then
fail "~/.openclaw/ is inside a git repo — API keys may be committed!" \
"Add .openclaw/ to .gitignore immediately: echo '.openclaw/' >> ~/.gitignore_global"
else
pass "~/.openclaw/ is NOT inside a git repo"
fi
fi
# =============================================================================
section "6. System Security"
# =============================================================================
# Check OS updates
if [[ "$OS" == "wsl2" || "$OS" == "linux" ]]; then
UPGRADABLE=$(apt list --upgradable 2>/dev/null | grep -c "upgradable" || echo "0")
if [[ "$UPGRADABLE" -gt 10 ]]; then
warn "$UPGRADABLE packages have available updates" \
"Update: sudo apt update && sudo apt upgrade -y"
elif [[ "$UPGRADABLE" -gt 0 ]]; then
info "$UPGRADABLE packages have available updates"
else
pass "System packages: up to date"
fi
elif [[ "$OS" == "macos" ]]; then
BREW_OUTDATED=$(brew outdated 2>/dev/null | wc -l | tr -d ' ')
if [[ "$BREW_OUTDATED" -gt 10 ]]; then
warn "$BREW_OUTDATED Homebrew packages are outdated" \
"Update: brew update && brew upgrade"
else
pass "Homebrew packages: mostly up to date ($BREW_OUTDATED outdated)"
fi
fi
# Check WSL2 systemd
if [[ "$OS" == "wsl2" ]]; then
if [[ -f /etc/wsl.conf ]] && grep -q "systemd=true" /etc/wsl.conf 2>/dev/null; then
pass "WSL2 systemd: enabled (daemon auto-start works)"
else
warn "WSL2 systemd not enabled — OpenClaw daemon won't auto-start" \
"Add [boot] systemd=true to /etc/wsl.conf and restart WSL (wsl --shutdown)"
fi
fi
# Check for open ports
if command -v ss &>/dev/null; then
OPEN_PORTS=$(ss -tlnp 2>/dev/null | grep -c "0.0.0.0\|\[::\]" || echo "0")
if [[ "$OPEN_PORTS" -gt 5 ]]; then
warn "$OPEN_PORTS services listening on all interfaces" \
"Review open ports: ss -tlnp | grep '0.0.0.0' — close anything you don't need"
else
pass "Open ports on all interfaces: $OPEN_PORTS (reasonable)"
fi
elif command -v lsof &>/dev/null; then
OPEN_PORTS=$(lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null | grep -c "\*:" || echo "0")
if [[ "$OPEN_PORTS" -gt 5 ]]; then
warn "$OPEN_PORTS services listening on all interfaces" \
"Review open ports: lsof -iTCP -sTCP:LISTEN -P -n | grep '\\*:' — close anything you don't need"
else
pass "Open ports on all interfaces: $OPEN_PORTS (reasonable)"
fi
fi
# =============================================================================
section "7. OpenClaw Doctor"
# =============================================================================
if command -v openclaw &>/dev/null; then
info "Running 'openclaw doctor' for built-in health check..."
echo ""
openclaw doctor 2>&1 | sed 's/^/ /' || warn "openclaw doctor failed" "Run manually: openclaw doctor"
echo ""
else
info "Skipping — OpenClaw not installed"
fi
# =============================================================================
# SUMMARY
# =============================================================================
echo ""
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BOLD} SECURITY SCAN SUMMARY${NC}"
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
TOTAL=$((PASS + FAIL + WARN))
echo -e " ${GREEN}✅ Passed: $PASS${NC}"
echo -e " ${RED}❌ Failed: $FAIL${NC}"
echo -e " ${YELLOW}⚠️ Warnings: $WARN${NC}"
echo -e " ─────────────────"
echo -e " ${BOLD}Total checks: $TOTAL${NC}"
echo ""
if [[ $FAIL -eq 0 && $WARN -eq 0 ]]; then
echo -e " ${GREEN}${BOLD}🎉 ALL CLEAR — Your OpenClaw setup is secure!${NC}"
elif [[ $FAIL -eq 0 ]]; then
echo -e " ${YELLOW}${BOLD}⚠️ MOSTLY SECURE — Review warnings below${NC}"
elif [[ $FAIL -le 2 ]]; then
echo -e " ${RED}${BOLD}🔴 ACTION REQUIRED — Fix the issues below before going live${NC}"
else
echo -e " ${RED}${BOLD}🚨 CRITICAL — Multiple security issues detected!${NC}"
fi
# Print recommendations
if [[ ${#RECOMMENDATIONS[@]} -gt 0 ]]; then
echo ""
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BOLD} RECOMMENDATIONS (fix in order)${NC}"
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
REC_NUM=1
for rec in "${RECOMMENDATIONS[@]}"; do
echo -e " ${BOLD}${REC_NUM}.${NC} $rec"
echo ""
((REC_NUM++))
done
fi
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e " Run this script again after fixing issues: ${BOLD}bash validate-security.sh${NC}"
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
# Exit with non-zero if any failures
if [[ $FAIL -gt 0 ]]; then
exit 1
fi
exit 0