learning_ai_common_plat/__LOCAL_LLMs/OPEN_CLAW/validate-security.sh
saravanakumardb1 5667308629 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
2026-02-22 15:38:52 -08:00

488 lines
20 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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