| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- #!/usr/bin/env bash
- # mac-ops :: firewall-audit.sh
- # Inventory macOS firewall state across all layers:
- # 1. Application Layer Firewall (ALF) — System Settings → Network → Firewall
- # 2. Packet Filter (pf) — BSD-level packet filtering
- # 3. Network Extension content filters (Little Snitch, Lulu, Cisco AnyConnect, etc.)
- # 4. Stealth mode + logging state
- set -u
- while [[ $# -gt 0 ]]; do
- case "$1" in
- --help|-h)
- cat <<EOF
- Usage: $0 [options]
- --json, --redact, --quiet, --verbose
- macOS firewall stack:
- ALF (socketfilterfw) Application-layer firewall — blocks incoming
- connections per-app. Visible in System Settings.
- pf (packet filter) BSD-style packet filtering. Configured via
- /etc/pf.conf and /etc/pf.anchors/. Usually
- inactive on desktop Macs.
- Network Extension filters Third-party (Little Snitch, Lulu, AnyConnect)
- implement custom filtering as content filters.
- Persist after app quit until disabled.
- EOF
- exit 0 ;;
- *) shift ;;
- esac
- done
- source "$(dirname "$0")/_lib/common.sh"
- parse_common_flags "$@"
- maybe_filter_self "$@"
- ALF="/usr/libexec/ApplicationFirewall/socketfilterfw"
- # ----------------------------------------------------------------------------
- section "1. APPLICATION LAYER FIREWALL (ALF)"
- # ----------------------------------------------------------------------------
- if [[ ! -x "$ALF" ]]; then
- log_warn "socketfilterfw binary" "not found at $ALF"
- else
- state=$("$ALF" --getglobalstate 2>/dev/null | tail -1)
- case "$state" in
- *"enabled"*) log_pass "ALF state" "$state" ;;
- *"disabled"*) log_warn "ALF state" "$state — incoming connections unblocked" ;;
- *) log_info "ALF state" "$state" ;;
- esac
- stealth=$("$ALF" --getstealthmode 2>/dev/null | tail -1)
- note " $stealth"
- block_all=$("$ALF" --getblockall 2>/dev/null | tail -1)
- note " $block_all"
- allow_signed=$("$ALF" --getallowsigned 2>/dev/null | tail -1)
- note " $allow_signed"
- # Per-app rules (may require sudo)
- rules=$("$ALF" --listapps 2>/dev/null | grep -c "^[0-9]" || echo 0)
- log_info "ALF per-app rules" "$rules"
- fi
- # ----------------------------------------------------------------------------
- section "2. PACKET FILTER (pf)"
- # ----------------------------------------------------------------------------
- if pf_info=$(pfctl -s info 2>&1); then
- if echo "$pf_info" | grep -q "Status: Enabled"; then
- log_warn "pf state" "Enabled — packet filter active"
- note " $(echo "$pf_info" | head -3 | sed 's/^/ /')"
- else
- log_pass "pf state" "Disabled (default for desktop Macs)"
- fi
- else
- log_info "pf state" "could not query (needs sudo)"
- fi
- # Anchors loaded
- if pf_anchors=$(sudo -n pfctl -s Anchors 2>/dev/null); then
- if [[ -n "$pf_anchors" ]]; then
- log_info "pf anchors loaded" "$(echo "$pf_anchors" | wc -l | tr -d ' ')"
- echo "$pf_anchors" | head -10 | sed 's/^/ /'
- fi
- fi
- # ----------------------------------------------------------------------------
- section "3. NETWORK EXTENSION CONTENT FILTERS"
- # ----------------------------------------------------------------------------
- # These are third-party filters that operate in their own NetworkExtension
- # rather than via ALF. They persist after the parent app quits.
- ne_filters=$(scutil --nc list 2>/dev/null | grep -iE "filter|firewall" || true)
- if [[ -n "$ne_filters" ]]; then
- log_info "Network Extension content filters" "$(echo "$ne_filters" | wc -l | tr -d ' ')"
- echo "$ne_filters" | sed 's/^/ /'
- else
- log_pass "Network Extension content filters" "none configured"
- fi
- # Check for common third-party firewall apps
- note ""
- note " Installed firewall/network-monitoring apps:"
- for app in "Little Snitch" "LuLu" "Murus" "Hands Off" "Radio Silence" "NetIQuette"; do
- if [[ -e "/Applications/$app.app" ]]; then
- note " /Applications/$app.app"
- fi
- done
- # ----------------------------------------------------------------------------
- section "4. FIREWALL LOG SAMPLE"
- # ----------------------------------------------------------------------------
- # Anything ALF dropped in last hour
- recent_blocks=$(log show --last 1h --style compact \
- --predicate 'process == "socketfilterfw" OR eventMessage CONTAINS "Deny"' \
- 2>/dev/null | grep -iE "(deny|block|drop)" | tail -5)
- if [[ -n "$recent_blocks" ]]; then
- log_info "Recent firewall denials (1h)" "see below"
- echo "$recent_blocks" | sed 's/^/ /'
- fi
- # ----------------------------------------------------------------------------
- section "5. VPN / TUNNEL CONFIGURATION"
- # ----------------------------------------------------------------------------
- note " Active network services (VPN/tunnel filter):"
- networksetup -listallnetworkservices 2>/dev/null | tail -n +2 | \
- grep -iE "vpn|tunnel|wireguard|openvpn|warp|nextdns|tailscale|cisco|anyconnect|proton|mullvad" | \
- sed 's/^/ /'
- # Active tunnels right now
- note ""
- note " Active utun interfaces:"
- ifconfig 2>/dev/null | awk '/^utun[0-9]+:/{ifn=$1; sub(":","",ifn)} /inet[6]? /{if(ifn!="" && $1!~/^fe80/){print " "ifn": "$0; ifn=""}}' | head -10
- # ----------------------------------------------------------------------------
- emit_summary
|