Spaces:
Sleeping
Sleeping
| # Mission Control Security Audit | |
| # Run: bash scripts/security-audit.sh [--env-file .env] | |
| set -euo pipefail | |
| SCORE=0 | |
| MAX_SCORE=0 | |
| ISSUES=() | |
| pass() { echo " [PASS] $1"; ((SCORE++)); ((MAX_SCORE++)); } | |
| fail() { echo " [FAIL] $1"; ISSUES+=("$1"); ((MAX_SCORE++)); } | |
| warn() { echo " [WARN] $1"; ((MAX_SCORE++)); } | |
| info() { echo " [INFO] $1"; } | |
| # Load .env if exists | |
| ENV_FILE="${1:-.env}" | |
| if [[ -f "$ENV_FILE" ]]; then | |
| while IFS='=' read -r key value; do | |
| [[ "$key" =~ ^#.*$ ]] && continue | |
| [[ -z "$key" ]] && continue | |
| declare "$key=$value" 2>/dev/null || true | |
| done < "$ENV_FILE" | |
| fi | |
| echo "=== Mission Control Security Audit ===" | |
| echo "" | |
| # 1. .env file permissions | |
| echo "--- File Permissions ---" | |
| if [[ -f "$ENV_FILE" ]]; then | |
| perms=$(stat -f '%A' "$ENV_FILE" 2>/dev/null || stat -c '%a' "$ENV_FILE" 2>/dev/null) | |
| if [[ "$perms" == "600" ]]; then | |
| pass ".env permissions are 600 (owner read/write only)" | |
| else | |
| fail ".env permissions are $perms (should be 600). Run: chmod 600 $ENV_FILE" | |
| fi | |
| else | |
| warn ".env file not found at $ENV_FILE" | |
| fi | |
| # 2. Default passwords check | |
| echo "" | |
| echo "--- Credentials ---" | |
| INSECURE_PASSWORDS=("admin" "password" "change-me-on-first-login" "changeme" "testpass123" "testpass1234") | |
| AUTH_PASS_VAL="${AUTH_PASS:-}" | |
| if [[ -z "$AUTH_PASS_VAL" ]]; then | |
| fail "AUTH_PASS is not set" | |
| else | |
| insecure=false | |
| for bad in "${INSECURE_PASSWORDS[@]}"; do | |
| if [[ "$AUTH_PASS_VAL" == "$bad" ]]; then | |
| insecure=true; break | |
| fi | |
| done | |
| if $insecure; then | |
| fail "AUTH_PASS is set to a known insecure default" | |
| elif [[ ${#AUTH_PASS_VAL} -lt 12 ]]; then | |
| fail "AUTH_PASS is too short (${#AUTH_PASS_VAL} chars, minimum 12)" | |
| else | |
| pass "AUTH_PASS is set to a non-default value (${#AUTH_PASS_VAL} chars)" | |
| fi | |
| fi | |
| API_KEY_VAL="${API_KEY:-}" | |
| if [[ -z "$API_KEY_VAL" || "$API_KEY_VAL" == "generate-a-random-key" ]]; then | |
| fail "API_KEY is not set or uses the default value" | |
| else | |
| pass "API_KEY is configured" | |
| fi | |
| # 3. Network config | |
| echo "" | |
| echo "--- Network Security ---" | |
| MC_ALLOWED="${MC_ALLOWED_HOSTS:-}" | |
| MC_ANY="${MC_ALLOW_ANY_HOST:-}" | |
| if [[ "$MC_ANY" == "1" || "$MC_ANY" == "true" ]]; then | |
| fail "MC_ALLOW_ANY_HOST is enabled (any host can connect)" | |
| elif [[ -n "$MC_ALLOWED" ]]; then | |
| pass "MC_ALLOWED_HOSTS is configured: $MC_ALLOWED" | |
| else | |
| warn "MC_ALLOWED_HOSTS is not set (defaults apply)" | |
| fi | |
| # 4. Cookie/HTTPS config | |
| echo "" | |
| echo "--- HTTPS & Cookies ---" | |
| COOKIE_SECURE="${MC_COOKIE_SECURE:-}" | |
| if [[ "$COOKIE_SECURE" == "1" || "$COOKIE_SECURE" == "true" ]]; then | |
| pass "MC_COOKIE_SECURE is enabled" | |
| else | |
| warn "MC_COOKIE_SECURE is not enabled (cookies sent over HTTP)" | |
| fi | |
| SAMESITE="${MC_COOKIE_SAMESITE:-strict}" | |
| if [[ "$SAMESITE" == "strict" ]]; then | |
| pass "MC_COOKIE_SAMESITE is strict" | |
| else | |
| warn "MC_COOKIE_SAMESITE is '$SAMESITE' (strict recommended)" | |
| fi | |
| HSTS="${MC_ENABLE_HSTS:-}" | |
| if [[ "$HSTS" == "1" ]]; then | |
| pass "HSTS is enabled" | |
| else | |
| warn "HSTS is not enabled (set MC_ENABLE_HSTS=1 for HTTPS deployments)" | |
| fi | |
| # 5. Rate limiting | |
| echo "" | |
| echo "--- Rate Limiting ---" | |
| RL_DISABLED="${MC_DISABLE_RATE_LIMIT:-}" | |
| if [[ "$RL_DISABLED" == "1" ]]; then | |
| fail "Rate limiting is disabled (MC_DISABLE_RATE_LIMIT=1)" | |
| else | |
| pass "Rate limiting is active" | |
| fi | |
| # 6. Docker security (if running in Docker) | |
| echo "" | |
| echo "--- Docker Security ---" | |
| if command -v docker &>/dev/null; then | |
| if docker ps --filter name=mission-control --format '{{.Names}}' 2>/dev/null | grep -q mission-control; then | |
| ro=$(docker inspect mission-control --format '{{.HostConfig.ReadonlyRootfs}}' 2>/dev/null || echo "false") | |
| if [[ "$ro" == "true" ]]; then | |
| pass "Container filesystem is read-only" | |
| else | |
| warn "Container filesystem is writable (use read_only: true)" | |
| fi | |
| nnp=$(docker inspect mission-control --format '{{.HostConfig.SecurityOpt}}' 2>/dev/null || echo "[]") | |
| if echo "$nnp" | grep -q "no-new-privileges"; then | |
| pass "no-new-privileges is set" | |
| else | |
| warn "no-new-privileges not set" | |
| fi | |
| user=$(docker inspect mission-control --format '{{.Config.User}}' 2>/dev/null || echo "") | |
| if [[ -n "$user" && "$user" != "root" && "$user" != "0" ]]; then | |
| pass "Container runs as non-root user ($user)" | |
| else | |
| warn "Container may be running as root" | |
| fi | |
| else | |
| info "Mission Control container not running" | |
| fi | |
| else | |
| info "Docker not installed (skipping container checks)" | |
| fi | |
| # Summary | |
| echo "" | |
| echo "=== Security Score: $SCORE / $MAX_SCORE ===" | |
| if [[ ${#ISSUES[@]} -gt 0 ]]; then | |
| echo "" | |
| echo "Issues to fix:" | |
| for issue in "${ISSUES[@]}"; do | |
| echo " - $issue" | |
| done | |
| fi | |
| if [[ $SCORE -eq $MAX_SCORE ]]; then | |
| echo "All checks passed!" | |
| elif [[ $SCORE -ge $((MAX_SCORE * 7 / 10)) ]]; then | |
| echo "Good security posture with minor improvements needed." | |
| else | |
| echo "Security improvements recommended before production use." | |
| fi | |