- 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
488 lines
20 KiB
Bash
Executable File
488 lines
20 KiB
Bash
Executable File
#!/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
|