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