#!/bin/bash # /usr/local/bin/opsec-preflight.sh — OPSEC Preflight Verification Gate # Verifies all required security controls are active for the current mode. # # Usage: # opsec-preflight.sh Human-readable HUD # opsec-preflight.sh --score Pass/fail count # opsec-preflight.sh --enforce Exit 1 if any applicable check fails # opsec-preflight.sh --base Only run base checks (standard mode) # opsec-preflight.sh --full Run base + ghost checks # opsec-preflight.sh --base --enforce Base checks, exit 1 on fail # opsec-preflight.sh --full --enforce Full checks, exit 1 on fail OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh" OPSEC_CONF="/etc/opsec/opsec.conf" PREFLIGHT_LOG="/var/log/opsec-preflight.log" # Source library if [ -f "$OPSEC_LIB" ]; then . "$OPSEC_LIB" opsec_load_config 2>/dev/null || true elif [ -f "$OPSEC_CONF" ]; then . "$OPSEC_CONF" fi # Fallback colors if lib not loaded type opsec_green >/dev/null 2>&1 || { opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; } opsec_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; } opsec_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; } opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; } opsec_cyan() { echo -e "\033[38;5;51m[>] $*\033[0m"; } opsec_dim() { echo -e "\033[38;5;244m $*\033[0m"; } opsec_hdr() { echo -e "\033[38;5;51m━━━ \033[38;5;201m$*\033[38;5;51m ━━━\033[0m"; } } # ─── PARSE ARGS ─────────────────────────────────────────────────────────────── MODE_SCORE=false MODE_ENFORCE=false CHECK_LEVEL="" while [ $# -gt 0 ]; do case "$1" in --score) MODE_SCORE=true ;; --enforce) MODE_ENFORCE=true ;; --base) CHECK_LEVEL="base" ;; --full) CHECK_LEVEL="full" ;; *) echo "Usage: opsec-preflight.sh [--score|--enforce] [--base|--full]"; exit 1 ;; esac shift done # Auto-detect check level if not specified if [ -z "$CHECK_LEVEL" ]; then if [ -f /var/run/opsec-advanced.enabled ]; then CHECK_LEVEL="full" else CHECK_LEVEL="base" fi fi # ─── ENVIRONMENT ────────────────────────────────────────────────────────────── LEVEL="${DEPLOYMENT_LEVEL:-bare-metal-standard}" LTYPE="${LEVEL_TYPE:-standard}" is_cloud() { case "$LEVEL" in cloud-*) return 0 ;; *) return 1 ;; esac } is_bare_metal() { case "$LEVEL" in bare-metal-*) return 0 ;; *) return 1 ;; esac } # ─── CHECK ENGINE ───────────────────────────────────────────────────────────── PASS=0 FAIL=0 SKIP=0 FAILURES="" check() { local name="$1" result="$2" detail="${3:-}" if [ "$result" = "pass" ]; then PASS=$((PASS + 1)) $MODE_SCORE || opsec_green "PASS ${name}${detail:+ ${detail}}" elif [ "$result" = "fail" ]; then FAIL=$((FAIL + 1)) FAILURES="${FAILURES}\n - ${name}${detail:+: ${detail}}" $MODE_SCORE || opsec_red "FAIL ${name}${detail:+ ${detail}}" elif [ "$result" = "skip" ]; then SKIP=$((SKIP + 1)) $MODE_SCORE || opsec_dim "SKIP ${name}${detail:+ (${detail})}" fi } # ─── BASE CHECKS (always run) ──────────────────────────────────────────────── run_base_checks() { $MODE_SCORE || opsec_cyan "Base Privacy Checks" # 1. MAC randomized (bare-metal only) if is_bare_metal; then local pif cur_mac first_octet first_dec pif=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1) if [ -n "$pif" ]; then cur_mac=$(ip link show "$pif" 2>/dev/null | awk '/ether/ {print $2}') first_octet=$(echo "$cur_mac" | cut -d: -f1) first_dec=$((16#${first_octet})) 2>/dev/null || first_dec=0 if (( first_dec & 2 )); then check "MAC randomized" "pass" "${pif}: ${cur_mac}" else check "MAC randomized" "fail" "${pif}: ${cur_mac} (hardware address)" fi else check "MAC randomized" "skip" "no primary interface" fi else check "MAC randomized" "skip" "cloud deployment" fi # 2. IPv6 disabled local ipv6_all ipv6_all=$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null || echo "0") if [ "$ipv6_all" = "1" ]; then check "IPv6 disabled" "pass" else check "IPv6 disabled" "fail" "net.ipv6.conf.all.disable_ipv6=${ipv6_all}" fi # 3. Core dumps disabled local core_pat core_pat=$(sysctl -n kernel.core_pattern 2>/dev/null) if [[ "$core_pat" == *"/bin/false"* ]] || [[ "$core_pat" == *"devnull"* ]]; then check "Core dumps disabled" "pass" else check "Core dumps disabled" "fail" "core_pattern=${core_pat}" fi # 4. Swap disabled local swap_count swap_count=$(swapon --show --noheadings 2>/dev/null | wc -l) if [ "$swap_count" -eq 0 ]; then check "Swap disabled" "pass" else check "Swap disabled" "fail" "${swap_count} swap device(s) active" fi # 5. DNS is privacy resolver (not ISP) local dns_server dns_server=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf 2>/dev/null) case "$dns_server" in 127.0.0.1|127.0.0.53|9.9.9.9|149.112.112.112|1.1.1.1|1.0.0.1) check "DNS privacy resolver" "pass" "${dns_server}" ;; *) check "DNS privacy resolver" "fail" "${dns_server} (possibly ISP)" ;; esac # 6. WebRTC blocked (if Firefox present) if [ -d "$HOME/.mozilla/firefox" ] || [ -d "/root/.mozilla/firefox" ]; then local userjs_found=false local profile_dirs profile_dirs=$(find /home/*/.mozilla/firefox /root/.mozilla/firefox -maxdepth 1 -name '*.default*' 2>/dev/null | head -3) for pd in $profile_dirs; do if [ -f "$pd/user.js" ] && grep -q "media.peerconnection.enabled.*false" "$pd/user.js" 2>/dev/null; then userjs_found=true break fi done if $userjs_found; then check "WebRTC blocked" "pass" "user.js configured" else check "WebRTC blocked" "fail" "no user.js with WebRTC disable found" fi else check "WebRTC blocked" "skip" "Firefox not detected" fi } # ─── GHOST CHECKS (only when ghost mode active) ────────────────────────────── run_ghost_checks() { $MODE_SCORE || opsec_cyan "Ghost Mode Checks" # 1. VPN or Tor active local vpn_if tor_ok=false vpn_if=$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | grep -E '^(tun|wg)' | head -1) ss -tln 2>/dev/null | grep -q ':9050 ' && tor_ok=true if [ -n "$vpn_if" ] || $tor_ok; then local detail="" [ -n "$vpn_if" ] && detail="VPN:${vpn_if}" $tor_ok && detail="${detail:+${detail} + }Tor:9050" check "VPN/Tor active" "pass" "$detail" else check "VPN/Tor active" "fail" "no tun/wg interface, no Tor on :9050" fi # 2. Kill switch armed if [ -f /var/run/opsec-breakglass.active ]; then check "Kill switch armed" "fail" "BREAK-GLASS active — kill switch intentionally dropped" elif iptables -L GP_FW >/dev/null 2>&1; then check "Kill switch armed" "pass" else check "Kill switch armed" "fail" "GP_FW chain not found" fi # 3. DNS locked to Tor local dns_server dns_server=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf 2>/dev/null) if [ "$dns_server" = "127.0.0.1" ]; then check "DNS locked to Tor" "pass" else check "DNS locked to Tor" "fail" "DNS=${dns_server} (expected 127.0.0.1)" fi # 4. Hostname randomized (bare-metal only) if is_bare_metal; then local cur_hostname cur_hostname=$(hostname) # Check if hostname looks randomized (not default patterns like kali, debian, localhost) case "$cur_hostname" in kali|debian|localhost|ubuntu|parrot) check "Hostname randomized" "fail" "hostname=${cur_hostname} (appears default)" ;; *) check "Hostname randomized" "pass" "${cur_hostname}" ;; esac else check "Hostname randomized" "skip" "cloud deployment" fi # 5. resolv.conf immutable if lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i'; then check "resolv.conf immutable" "pass" else check "resolv.conf immutable" "fail" "chattr +i not set" fi } # ─── EXECUTE CHECKS ────────────────────────────────────────────────────────── if ! $MODE_SCORE; then echo "" opsec_hdr "OPSEC PREFLIGHT — ${CHECK_LEVEL^^}" opsec_dim "Level: ${LEVEL} (${LTYPE})" echo "" fi run_base_checks if [ "$CHECK_LEVEL" = "full" ]; then $MODE_SCORE || echo "" run_ghost_checks fi # ─── RESULTS ────────────────────────────────────────────────────────────────── TOTAL=$((PASS + FAIL)) if $MODE_SCORE; then echo "${PASS}/${TOTAL} passed" [ "$FAIL" -gt 0 ] && exit 1 exit 0 fi echo "" opsec_hdr "PREFLIGHT RESULT" if [ "$FAIL" -eq 0 ]; then opsec_green "ALL CHECKS PASSED (${PASS}/${TOTAL})" [ "$SKIP" -gt 0 ] && opsec_dim "${SKIP} checks skipped (not applicable)" else opsec_red "FAILED: ${FAIL}/${TOTAL} checks" opsec_dim "Passed: ${PASS} | Failed: ${FAIL} | Skipped: ${SKIP}" echo -e "\033[38;5;196mFailures:${FAILURES}\033[0m" # Log failures echo "[$(date -Is)] PREFLIGHT ${CHECK_LEVEL^^}: ${PASS}/${TOTAL} passed, ${FAIL} failed${FAILURES}" >> "$PREFLIGHT_LOG" 2>/dev/null || true fi echo "" if $MODE_ENFORCE && [ "$FAIL" -gt 0 ]; then exit 1 fi exit 0