#!/usr/bin/env bash # audit_security.sh — Security posture assessment for macOS (read-only) # Output: JSON lines to stdout, one per finding set -euo pipefail json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' '; } json_finding() { local severity="$1" finding action details finding=$(json_escape "$2") action=$(json_escape "$3") details=$(json_escape "$4") printf '{"module":"security","severity":"%s","finding":"%s","action":"%s","details":"%s"}\n' \ "$severity" "$finding" "$action" "$details" } # --- SIP (System Integrity Protection) --- sip_status=$(csrutil status 2>/dev/null || echo "unknown") if echo "$sip_status" | grep -q "enabled"; then json_finding "info" "SIP enabled" "none" "" elif echo "$sip_status" | grep -q "disabled"; then json_finding "critical" "SIP is disabled" "report" "System Integrity Protection should be enabled" else json_finding "warning" "SIP status unknown" "report" "$sip_status" fi # --- Gatekeeper --- gatekeeper_status=$(spctl --status 2>/dev/null || echo "unknown") if echo "$gatekeeper_status" | grep -q "assessments enabled"; then json_finding "info" "Gatekeeper enabled" "none" "" elif echo "$gatekeeper_status" | grep -q "assessments disabled"; then json_finding "critical" "Gatekeeper is disabled" "report" "Enable via: sudo spctl --master-enable" else json_finding "warning" "Gatekeeper status unknown" "report" "$gatekeeper_status" fi # --- Firewall --- fw_tool="/usr/libexec/ApplicationFirewall/socketfilterfw" if [ -x "$fw_tool" ]; then fw_status=$("$fw_tool" --getglobalstate 2>/dev/null || echo "unknown") if echo "$fw_status" | grep -qi "enabled"; then json_finding "info" "Firewall enabled" "none" "" else json_finding "critical" "Firewall is disabled" "report" "Enable in System Settings > Network > Firewall" fi else # Fallback to defaults fw_default=$(defaults read /Library/Preferences/com.apple.alf globalstate 2>/dev/null || echo "unknown") case "$fw_default" in 0) json_finding "critical" "Firewall is disabled" "report" "Enable in System Settings > Network > Firewall" ;; 1|2) json_finding "info" "Firewall enabled" "none" "" ;; *) json_finding "warning" "Firewall status unknown" "report" "" ;; esac fi # --- FileVault --- fv_status=$(fdesetup status 2>/dev/null || echo "unknown") if echo "$fv_status" | grep -q "FileVault is On"; then json_finding "info" "FileVault enabled (disk encryption)" "none" "" elif echo "$fv_status" | grep -q "FileVault is Off"; then json_finding "critical" "FileVault is off (disk not encrypted)" "report" "Enable in System Settings > Privacy and Security > FileVault" else json_finding "warning" "FileVault status unknown" "report" "$fv_status" fi # --- Open listening ports --- listening_ports=$(lsof -iTCP -sTCP:LISTEN -nP 2>/dev/null | tail -n +2 || echo "") port_count=$(echo "$listening_ports" | grep -c . 2>/dev/null || echo 0) if [ "$port_count" -gt 0 ]; then # Extract unique port/process combos port_summary=$(echo "$listening_ports" | awk '{print $1":"$9}' | sort -u | head -10 | tr '\n' ', ' | sed 's/,$//') json_finding "info" "${port_count} listening TCP connections" "review" "$port_summary" fi # --- SSH config --- ssh_config="/etc/ssh/sshd_config" if [ -f "$ssh_config" ]; then # Check password auth if grep -qi "^PasswordAuthentication yes" "$ssh_config" 2>/dev/null; then json_finding "warning" "SSH password authentication enabled" "report" "Consider key-based auth only" fi # Check root login if grep -qi "^PermitRootLogin yes" "$ssh_config" 2>/dev/null; then json_finding "warning" "SSH root login permitted" "report" "Consider disabling root SSH login" fi fi # --- Remote login --- remote_login=$(systemsetup -getremotelogin 2>/dev/null || echo "unknown") if echo "$remote_login" | grep -qi "on"; then json_finding "info" "Remote Login (SSH) is enabled" "review" "Disable if not needed: System Settings > General > Sharing" fi # --- Remote management --- remote_mgmt=$(defaults read /Library/Preferences/com.apple.RemoteManagement ARD_AllLocalUsers 2>/dev/null || echo "not configured") if [ "$remote_mgmt" = "1" ]; then json_finding "warning" "Remote Management enabled for all users" "review" "Check System Settings > General > Sharing" fi # --- Software updates --- # Just check if updates are available (this can be slow, so we time-limit it) # macOS lacks GNU timeout — use perl alarm as a portable alternative update_check=$(perl -e 'alarm 30; exec @ARGV' -- softwareupdate -l 2>&1 || echo "timeout or error") if echo "$update_check" | grep -q "No new software available"; then json_finding "info" "macOS software is up to date" "none" "" elif echo "$update_check" | grep -q "timeout or error"; then json_finding "info" "Software update check timed out" "none" "Run softwareupdate -l manually" else update_count=$(echo "$update_check" | grep -c "^\*" 2>/dev/null || echo 0) json_finding "warning" "${update_count} software update(s) available" "softwareupdate" "Run: softwareupdate -l for details" fi # --- Screen lock --- screen_lock_delay=$(defaults -currentHost read com.apple.screensaver idleTime 2>/dev/null || echo "not set") ask_password=$(defaults read com.apple.screensaver askForPassword 2>/dev/null || echo "unknown") if [ "$ask_password" = "0" ]; then json_finding "warning" "Screen lock password not required" "report" "Enable in System Settings > Lock Screen" elif [ "$screen_lock_delay" != "not set" ] && [ "$screen_lock_delay" -gt 300 ] 2>/dev/null; then json_finding "info" "Screen lock delay: ${screen_lock_delay}s (consider reducing)" "report" "" fi