Network Operations Diagnose network problems on Windows, macOS, or Linux with a layered ladder that isolates faults to the smallest possible scope, then pattern-match against OS-specific culprits. Designed for the common case: someone reports "internet broken" on a box you can shell into (locally or via SSH). The Universal Insight Bypass-tool succeeds while OS-resolver fails is a smoking gun on every platform. It means DNS infrastructure is healthy but the operating system's name-resolution path is hooked or misconfigured. The bypass tool differs per OS but the discriminator is identical: | O…

\\n'/\\\\n}\"\n s=\"${s//

Network Operations Diagnose network problems on Windows, macOS, or Linux with a layered ladder that isolates faults to the smallest possible scope, then pattern-match against OS-specific culprits. Designed for the common case: someone reports "internet broken" on a box you can shell into (locally or via SSH). The Universal Insight Bypass-tool succeeds while OS-resolver fails is a smoking gun on every platform. It means DNS infrastructure is healthy but the operating system's name-resolution path is hooked or misconfigured. The bypass tool differs per OS but the discriminator is identical: | O…

\\r'/\\\\r}\"\n s=\"${s//

Network Operations Diagnose network problems on Windows, macOS, or Linux with a layered ladder that isolates faults to the smallest possible scope, then pattern-match against OS-specific culprits. Designed for the common case: someone reports "internet broken" on a box you can shell into (locally or via SSH). The Universal Insight Bypass-tool succeeds while OS-resolver fails is a smoking gun on every platform. It means DNS infrastructure is healthy but the operating system's name-resolution path is hooked or misconfigured. The bypass tool differs per OS but the discriminator is identical: | O…

\\t'/\\\\t}\"\n printf '%s' \"$s\"\n}\n\n# These three are the public API. They write either text or JSON depending on mode.\nPASS_COUNT=0\nFAIL_COUNT=0\nFIRST_FAIL=\"\"\nCURRENT_SECTION=\"\"\n\nsection() {\n CURRENT_SECTION=\"$1\"\n if [[ \"$JSON_MODE\" -eq 1 ]]; then\n printf '{\"type\":\"section\",\"name\":\"%s\"}\\n' \"$(_json_escape \"$1\")\"\n else\n echo\n echo \"=== $1 ===\"\n fi\n}\n\npass() {\n PASS_COUNT=$((PASS_COUNT + 1))\n if [[ \"$JSON_MODE\" -eq 1 ]]; then\n printf '{\"type\":\"check\",\"section\":\"%s\",\"label\":\"%s\",\"status\":\"pass\",\"detail\":\"%s\"}\\n' \\\n \"$(_json_escape \"$CURRENT_SECTION\")\" \"$(_json_escape \"$1\")\" \"$(_json_escape \"${2:-}\")\"\n else\n echo \"[PASS] $1${2:+ :: $2}\"\n fi\n}\n\nfail() {\n FAIL_COUNT=$((FAIL_COUNT + 1))\n [[ -z \"$FIRST_FAIL\" ]] && FIRST_FAIL=\"[$CURRENT_SECTION] $1\"\n if [[ \"$JSON_MODE\" -eq 1 ]]; then\n printf '{\"type\":\"check\",\"section\":\"%s\",\"label\":\"%s\",\"status\":\"fail\",\"detail\":\"%s\"}\\n' \\\n \"$(_json_escape \"$CURRENT_SECTION\")\" \"$(_json_escape \"$1\")\" \"$(_json_escape \"${2:-}\")\"\n else\n echo \"[FAIL] $1${2:+ :: $2}\"\n fi\n}\n\n# Call from end of probe to emit summary record / block.\nemit_summary() {\n if [[ \"$JSON_MODE\" -eq 1 ]]; then\n printf '{\"type\":\"summary\",\"pass\":%d,\"fail\":%d,\"first_fail\":\"%s\"}\\n' \\\n \"$PASS_COUNT\" \"$FAIL_COUNT\" \"$(_json_escape \"$FIRST_FAIL\")\"\n else\n echo\n echo \"=== SUMMARY ===\"\n echo \" PASS: $PASS_COUNT FAIL: $FAIL_COUNT\"\n if [[ -n \"$FIRST_FAIL\" ]]; then\n echo \" First failure: $FIRST_FAIL\"\n else\n echo \" No failures.\"\n fi\n fi\n}\n\n# Helper for scripts that want to suppress informational/diagnostic output\n# (the non-PASS/FAIL annotations like scutil dumps) in JSON mode.\ninfo() {\n if [[ \"$JSON_MODE\" -eq 1 ]]; then\n # Optional: emit info records. Keep silent for cleaner JSON parsing.\n return 0\n fi\n echo \"$@\"\n}\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2728,"content_sha256":"6d91f8d34bb2c8c54be6bd23b07f9b24c2855d568dddd4afcf35f7d33b6450ff"},{"filename":"scripts/_lib/redact.sh","content":"# net-ops :: _lib/redact.sh\n# Shared opsec redaction for diagnostic output. Source from any bash script:\n#\n# source \"$(dirname \"$0\")/../_lib/redact.sh\"\n# parse_redact_flag \"$@\"\n# maybe_redact_self \"$@\" # re-invokes self without --redact if flag set\n#\n# Public IPs (1.1.1.1, 8.8.8.8, Tailscale 100.100.100.100 anchor) are\n# preserved — they're diagnostic landmarks. Private/CGNAT/link-local\n# ranges, MACs, and *.ts.net tailnet names are masked.\n\nREDACT=\"${REDACT:-0}\"\n\nparse_redact_flag() {\n for a in \"$@\"; do\n [[ \"$a\" == \"--redact\" ]] && REDACT=1\n done\n}\n\nredact_filter() {\n if [[ \"${REDACT:-0}\" -eq 0 ]]; then cat; return; fi\n perl -pe '\n # Preserve well-known anchors first\n s/100\\.100\\.100\\.100/__TS_MAGIC__/g;\n # Redact private / CGNAT / link-local IPv4\n s/\\b10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/10.X.X.X/g;\n s/\\b172\\.(1[6-9]|2[0-9]|3[01])\\.\\d{1,3}\\.\\d{1,3}\\b/172.X.X.X/g;\n s/\\b192\\.168\\.\\d{1,3}\\.\\d{1,3}\\b/192.168.X.X/g;\n s/\\b100\\.(6[4-9]|[7-9]\\d|1[01]\\d|12[0-7])\\.\\d{1,3}\\.\\d{1,3}\\b/100.X.X.X/g;\n s/\\b169\\.254\\.\\d{1,3}\\.\\d{1,3}\\b/169.254.X.X/g;\n # MAC addresses (both : and - separators)\n s/\\b[0-9a-fA-F]{2}([:-])[0-9a-fA-F]{2}\\1[0-9a-fA-F]{2}\\1[0-9a-fA-F]{2}\\1[0-9a-fA-F]{2}\\1[0-9a-fA-F]{2}\\b/XX:XX:XX:XX:XX:XX/g;\n # Tailscale tailnet names\n s/\\b[a-z0-9-]+\\.ts\\.net\\b/REDACTED.ts.net/g;\n # Restore anchors\n s/__TS_MAGIC__/100.100.100.100/g;\n '\n}\n\n# Helper: self-reinvoke and pipe through post-processing filters when needed.\n# Handles both --redact (mask private addrs) and --json (drop non-JSON chatter).\n# Avoids bash 3.2 exec-redirect quirks via single-level subprocess.\nmaybe_redact_self() {\n # Only reinvoke if at least one filter is active\n [[ \"${REDACT:-0}\" -eq 1 ]] || [[ \"${JSON_MODE:-0}\" -eq 1 ]] || return 0\n # Prevent infinite recursion\n [[ \"${_NETOPS_POSTPROCESSED:-0}\" -eq 1 ]] && return 0\n export _NETOPS_POSTPROCESSED=1\n\n # Strip --redact from args (child runs without it to avoid double-recursion).\n # --json is preserved so JSON_MODE stays set in the child for any code that\n # changes behavior in JSON mode (e.g. info() suppression in output.sh).\n local cleaned_args=()\n for a in \"$@\"; do [[ \"$a\" != \"--redact\" ]] && cleaned_args+=(\"$a\"); done\n\n if [[ \"${JSON_MODE:-0}\" -eq 1 ]] && [[ \"${REDACT:-0}\" -eq 1 ]]; then\n \"$0\" ${cleaned_args[@]+\"${cleaned_args[@]}\"} | grep '^{' | redact_filter\n elif [[ \"${JSON_MODE:-0}\" -eq 1 ]]; then\n \"$0\" ${cleaned_args[@]+\"${cleaned_args[@]}\"} | grep '^{'\n else\n \"$0\" ${cleaned_args[@]+\"${cleaned_args[@]}\"} | redact_filter\n fi\n exit \"${PIPESTATUS[0]}\"\n}\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2707,"content_sha256":"c197a0a89f927ac7e7deb3a6ffa86d413112592a012eed6c691d518a5e44f1f2"},{"filename":"scripts/linux/dns-audit.sh","content":"#!/usr/bin/env bash\n# net-ops :: linux/dns-audit.sh\n# Deep DNS forensics for Linux. Use when probe.sh shows rung 4 (dig) PASS\n# but rung 5 (getent / resolvectl) FAIL.\n\nset -u\n\n# shellcheck source=../_lib/redact.sh\nsource \"$(dirname \"$0\")/../_lib/redact.sh\"\nparse_redact_flag \"$@\"\nmaybe_redact_self \"$@\"\n\necho \"=== /etc/nsswitch.conf (hosts line) ===\"\ngrep \"^hosts:\" /etc/nsswitch.conf 2>/dev/null || echo \" (no hosts entry)\"\n\necho\necho \"=== /etc/resolv.conf ===\"\nif [[ -L /etc/resolv.conf ]]; then\n echo \" Type: symlink -> $(readlink /etc/resolv.conf)\"\nelse\n echo \" Type: regular file\"\nfi\necho \" Modified: $(stat -c '%y' /etc/resolv.conf 2>/dev/null || stat -f '%Sm' /etc/resolv.conf 2>/dev/null)\"\necho \" --- contents ---\"\ncat /etc/resolv.conf 2>/dev/null | sed 's/^/ /'\n\necho\necho \"=== systemd-resolved ===\"\nif systemctl is-active systemd-resolved >/dev/null 2>&1; then\n echo \" Service: active\"\n echo \" --- resolvectl status ---\"\n resolvectl status 2>/dev/null | sed 's/^/ /'\nelse\n echo \" Service: inactive or not installed\"\nfi\n\necho\necho \"=== NetworkManager DNS config ===\"\nif command -v nmcli >/dev/null 2>&1; then\n echo \" --- nmcli dev show (DNS lines) ---\"\n nmcli dev show 2>/dev/null | grep -E 'DEVICE|IP4.DNS|IP6.DNS|DOMAIN' | sed 's/^/ /'\n echo\n echo \" --- NetworkManager dns mode ---\"\n awk '/\\[main\\]/,/\\[/{if(/^dns/) print}' /etc/NetworkManager/NetworkManager.conf 2>/dev/null | sed 's/^/ /' || true\n ls -la /etc/NetworkManager/conf.d/ 2>/dev/null | sed 's/^/ /' || true\nelse\n echo \" nmcli not installed\"\nfi\n\necho\necho \"=== dnsmasq ===\"\nif pgrep -x dnsmasq >/dev/null; then\n pid=$(pgrep -x dnsmasq | head -1)\n echo \" Running, PID $pid\"\n ps -o command -p \"$pid\" 2>/dev/null | sed 's/^/ /'\nelse\n echo \" not running\"\nfi\nfor d in /etc/dnsmasq.d /etc/NetworkManager/dnsmasq.d; do\n [[ -d \"$d\" ]] && { echo \" $d contents:\"; ls \"$d\" 2>/dev/null | sed 's/^/ /'; }\ndone\n\necho\necho \"=== Local DNS listeners ===\"\nss -tulnp 2>/dev/null | awk 'NR==1 || $5 ~ /:53$/' | sed 's/^/ /'\n\necho\necho \"=== /etc/hosts (non-comment) ===\"\ngrep -vE '^\\s*(#|$)' /etc/hosts 2>/dev/null | sed 's/^/ /' || echo \" (no custom entries)\"\n\necho\necho \"=== VPN / WireGuard interfaces ===\"\nip -br link 2>/dev/null | awk '/^(wg|tun|tap|nordlynx|proton|mullvad|nextdns)/' | sed 's/^/ /' || true\nif command -v wg >/dev/null 2>&1; then\n echo \" --- wg show ---\"\n wg show 2>/dev/null | sed 's/^/ /' | head -30 || true\nfi\n\necho\necho \"=== ATTRIBUTION HINTS ===\"\n# Inspect nameservers visible across the stack for known patterns\nns_list=$( {\n awk '/^nameserver/{print $2}' /etc/resolv.conf 2>/dev/null\n resolvectl status 2>/dev/null | awk '/Current DNS Server:|DNS Servers:/{for(i=4;i\u003c=NF;i++)print $i}'\n nmcli -t -f IP4.DNS,IP6.DNS dev show 2>/dev/null | awk -F: '{print $2}'\n} | sort -u | grep -v '^

Network Operations Diagnose network problems on Windows, macOS, or Linux with a layered ladder that isolates faults to the smallest possible scope, then pattern-match against OS-specific culprits. Designed for the common case: someone reports "internet broken" on a box you can shell into (locally or via SSH). The Universal Insight Bypass-tool succeeds while OS-resolver fails is a smoking gun on every platform. It means DNS infrastructure is healthy but the operating system's name-resolution path is hooked or misconfigured. The bypass tool differs per OS but the discriminator is identical: | O…

)\n\nwhile read -r n; do\n [[ -z \"$n\" ]] && continue\n case \"$n\" in\n 10.2.0.*) echo \" $n :: likely Proton VPN gateway\" ;;\n 10.64.0.*) echo \" $n :: likely Mullvad gateway\" ;;\n 10.211.*|10.212.*) echo \" $n :: likely Cisco AnyConnect\" ;;\n 100.100.100.100) echo \" $n :: Tailscale MagicDNS (expected)\" ;;\n 127.0.0.53) echo \" $n :: systemd-resolved stub (expected on most systems)\" ;;\n 127.0.0.1|127.0.0.2) echo \" $n :: local DNS proxy (dnsmasq, NextDNS, AdGuard, etc.)\" ;;\n esac\ndone \u003c\u003c\u003c \"$ns_list\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3459,"content_sha256":"15b20bc84bb12310f77b248c19af4c03a6204d291bca8552f74ef33831ba5067"},{"filename":"scripts/linux/probe.sh","content":"#!/usr/bin/env bash\n# net-ops :: linux/probe.sh\n# Full layered diagnostic ladder for Linux network troubleshooting.\n# Outputs structured [PASS]/[FAIL] lines so a human or LLM can scan for\n# the first FAIL and drill in.\n\nset -u\n\nTEST_HOST=\"${TEST_HOST:-google.com}\"\nTEST_IPS=(\"1.1.1.1\" \"8.8.8.8\")\nTIMEOUT=\"${TIMEOUT:-5}\"\n\n# shellcheck source=../_lib/redact.sh\nsource \"$(dirname \"$0\")/../_lib/redact.sh\"\n# shellcheck source=../_lib/output.sh\nsource \"$(dirname \"$0\")/../_lib/output.sh\"\nparse_redact_flag \"$@\"\nparse_output_flags \"$@\"\nmaybe_redact_self \"$@\"\n\n# ---------------------------------------------------------------------------\nsection \"1. LINK LAYER\"\n# ---------------------------------------------------------------------------\nip -br link 2>/dev/null | awk '$2==\"UP\"{print $1}' | while read -r dev; do\n [[ \"$dev\" == \"lo\" ]] && continue\n addr=$(ip -br -4 addr show \"$dev\" 2>/dev/null | awk '{print $3}')\n pass \"Interface $dev UP\" \"${addr:-no IPv4}\"\ndone\n\nGATEWAY=$(ip route show default 2>/dev/null | awk '/default/{print $3; exit}')\nDEFAULT_IF=$(ip route show default 2>/dev/null | awk '/default/{print $5; exit}')\n[[ -n \"$GATEWAY\" ]] && pass \"Default gateway\" \"$GATEWAY via $DEFAULT_IF\" || fail \"Default gateway\" \"none configured\"\n\n# ---------------------------------------------------------------------------\nsection \"2. IP / ICMP REACHABILITY\"\n# ---------------------------------------------------------------------------\n[[ -n \"${GATEWAY:-}\" ]] && {\n if ping -c 2 -W \"$TIMEOUT\" \"$GATEWAY\" >/dev/null 2>&1; then pass \"Ping gateway $GATEWAY\"; else fail \"Ping gateway $GATEWAY\"; fi\n}\nfor ip in \"${TEST_IPS[@]}\"; do\n if ping -c 2 -W \"$TIMEOUT\" \"$ip\" >/dev/null 2>&1; then pass \"Ping $ip\"; else fail \"Ping $ip\"; fi\ndone\n\n# ---------------------------------------------------------------------------\nsection \"3. TCP/UDP SOCKET REACHABILITY\"\n# ---------------------------------------------------------------------------\nfor ip in \"${TEST_IPS[@]}\"; do\n if timeout \"$TIMEOUT\" bash -c \"\u003c/dev/tcp/$ip/443\" 2>/dev/null; then pass \"TCP/443 -> $ip\"; else fail \"TCP/443 -> $ip\"; fi\n if timeout \"$TIMEOUT\" bash -c \"\u003c/dev/tcp/$ip/53\" 2>/dev/null; then pass \"TCP/53 -> $ip\"; else fail \"TCP/53 -> $ip\"; fi\ndone\n\n# Raw UDP/53 via dig with explicit server — bypasses /etc/resolv.conf\nfor ip in \"${TEST_IPS[@]}\"; do\n if result=$(dig +short +time=\"$TIMEOUT\" +tries=1 @\"$ip\" \"$TEST_HOST\" 2>&1) && [[ -n \"$result\" ]] && [[ ! \"$result\" =~ \"timed out\"|\"connection refused\" ]]; then\n pass \"UDP/53 -> $ip (dig)\" \"$(echo \"$result\" | head -1)\"\n else\n fail \"UDP/53 -> $ip (dig)\" \"$result\"\n fi\ndone\n\n# ---------------------------------------------------------------------------\nsection \"4. DNS INFRASTRUCTURE (bypass tools)\"\n# ---------------------------------------------------------------------------\n# dig uses its own resolver — does NOT touch glibc NSS chain\nfor srv in \"\" \"${TEST_IPS[@]}\"; do\n if [[ -z \"$srv\" ]]; then\n out=$(dig +short +time=\"$TIMEOUT\" +tries=1 \"$TEST_HOST\" 2>&1)\n label=\"default\"\n else\n out=$(dig +short +time=\"$TIMEOUT\" +tries=1 @\"$srv\" \"$TEST_HOST\" 2>&1)\n label=\"$srv\"\n fi\n if [[ -n \"$out\" && ! \"$out\" =~ \"timed out\"|\"connection refused\" ]]; then\n pass \"dig via $label\" \"$(echo \"$out\" | head -1)\"\n else\n fail \"dig via $label\" \"$out\"\n fi\ndone\n\n# ---------------------------------------------------------------------------\nsection \"5. LINUX RESOLVER PATH (the hook layer)\"\n# ---------------------------------------------------------------------------\n# getent uses glibc NSS — goes through the whole system resolver chain\nif out=$(getent hosts \"$TEST_HOST\" 2>&1) && [[ -n \"$out\" ]]; then\n addr=$(echo \"$out\" | awk '{print $1; exit}')\n pass \"getent hosts (NSS path)\" \"$addr\"\nelse\n fail \"getent hosts (NSS path)\" \"$out\"\nfi\n\n# resolvectl query if systemd-resolved present\nif command -v resolvectl >/dev/null 2>&1; then\n if out=$(resolvectl query \"$TEST_HOST\" 2>&1) && echo \"$out\" | grep -q \"^$TEST_HOST:\"; then\n addr=$(echo \"$out\" | awk '/^[^:]+:.+[0-9]+\\./{print $2; exit}')\n pass \"resolvectl query\" \"$addr\"\n else\n fail \"resolvectl query\" \"$(echo \"$out\" | head -2)\"\n fi\nfi\n\n# nsswitch.conf — name resolution order\necho \" /etc/nsswitch.conf hosts line:\"\ngrep \"^hosts:\" /etc/nsswitch.conf 2>/dev/null | sed 's/^/ /'\n\n# /etc/resolv.conf — is it the systemd-resolved stub, NetworkManager's, or static?\necho \" /etc/resolv.conf:\"\nif [[ -L /etc/resolv.conf ]]; then\n target=$(readlink /etc/resolv.conf)\n echo \" symlink -> $target\"\nfi\nhead -5 /etc/resolv.conf 2>/dev/null | sed 's/^/ /'\n\n# Active resolver listeners on 127.x:53\necho \" Local DNS listeners on 127.0.0.x:53:\"\nss -tulnp 2>/dev/null | awk '$5 ~ /^127\\./ && $5 ~ /:53$/' | sed 's/^/ /' || true\n\n# systemd-resolved status (if present)\nif systemctl is-active systemd-resolved >/dev/null 2>&1; then\n echo \" systemd-resolved active. Per-link DNS:\"\n resolvectl status 2>/dev/null | awk '\n /^Link [0-9]+/{link=$0; show=0; printed=0}\n /Current DNS Server:|DNS Servers:|DNS Domain:/{\n if(!printed){print \" \"link; printed=1}\n print \" \"$0\n }\n ' | head -40\nfi\n\n# ---------------------------------------------------------------------------\n# Time-sync deep-dive: HTTP Date drift + check timedatectl/chrony/ntpd status\nremote_date=$(curl -sIA 'net-ops-probe' --max-time 5 https://www.google.com 2>/dev/null | awk -F': ' 'tolower($1)==\"date\"{print $2; exit}' | tr -d '\\r')\ndrift_ok=1\ndrift_detail=\"\"\nif [[ -n \"$remote_date\" ]]; then\n remote_epoch=$(date -d \"$remote_date\" +%s 2>/dev/null)\n if [[ -n \"$remote_epoch\" ]]; then\n local_epoch=$(date +%s)\n drift=$(( local_epoch - remote_epoch ))\n abs_drift=${drift#-}\n if [[ \"$abs_drift\" -lt 300 ]]; then\n drift_detail=\"${drift}s vs HTTP Date (within ±5min)\"\n else\n drift_ok=0\n drift_detail=\"${drift}s drift — will break TLS cert validation\"\n fi\n fi\nfi\n\n# Detect which time daemon and its sync state\nsync_detail=\"\"\nif command -v timedatectl >/dev/null 2>&1; then\n sync_state=$(timedatectl show 2>/dev/null | awk -F= '/^NTPSynchronized=/{print $2}')\n sync_detail=\"systemd-timesyncd NTPSynchronized=$sync_state\"\nelif command -v chronyc >/dev/null 2>&1; then\n stratum=$(chronyc tracking 2>/dev/null | awk -F': ' '/Stratum/{print $2}')\n sync_detail=\"chronyd stratum=$stratum\"\n [[ \"$stratum\" == \"16\" ]] && drift_ok=0\nelif command -v ntpq >/dev/null 2>&1; then\n sync_detail=\"ntpd present (run 'ntpq -p' for peer status)\"\nfi\n\ncombined=\"$drift_detail${sync_detail:+; $sync_detail}\"\nif [[ \"$drift_ok\" -eq 1 ]]; then\n pass \"Time sync\" \"$combined\"\nelse\n fail \"Time sync\" \"$combined\"\nfi\n\n# MTU / path-MTU discovery. Linux uses -M do (don't fragment).\nif ping -M do -s 1472 -c 1 -W 3 1.1.1.1 >/dev/null 2>&1; then\n pass \"Path MTU 1500 (1472-byte DF payload)\" \"to 1.1.1.1\"\nelse\n if ping -M do -s 1400 -c 1 -W 3 1.1.1.1 >/dev/null 2>&1; then\n fail \"Path MTU 1500 (1472-byte DF payload)\" \"1500 fails, 1428+ works — path MTU \u003c 1500 (VPN/PPPoE?)\"\n else\n pass \"Path MTU test inconclusive\" \"ICMP DF blocked or destination unreachable\"\n fi\nfi\n\n# IPv6 deep-dive — classifies v6 stack state across four meaningful tiers.\nv6_state=\"\"\nv6_detail=\"\"\n\nv6_addrs=$(ip -6 -br addr show scope global 2>/dev/null | awk '{for(i=3;i\u003c=NF;i++) print $1\" \"$i}' | grep -v '^lo ')\nv6_global=$(printf '%s\\n' \"$v6_addrs\" | awk '$2 !~ /^fd/ && $2 !~ /^fc/{print; exit}')\nv6_default=$(ip -6 route show default 2>/dev/null | head -1)\n\nif [[ -z \"$v6_addrs\" ]]; then\n v6_state=\"disabled\"\n v6_detail=\"no global v6 addresses — IPv6 disabled or unconfigured (check sysctl net.ipv6.conf.all.disable_ipv6)\"\nelif [[ -z \"$v6_global\" ]]; then\n v6_state=\"ula_only\"\n v6_detail=\"only ULA (fc00::/7) addresses present — router not delegating public v6 prefix\"\nelif [[ -z \"$v6_default\" ]]; then\n v6_state=\"no_route\"\n v6_detail=\"global v6 address present but no default route — RA not received (check accept_ra sysctl)\"\nelse\n aaaa=$(dig +short +time=2 +tries=1 AAAA \"$TEST_HOST\" 2>/dev/null | head -1)\n if [[ -n \"$aaaa\" ]] && curl -6 -sS -o /dev/null --max-time 4 \"https://$TEST_HOST\" 2>/dev/null; then\n v6_state=\"healthy\"\n v6_detail=\"global addr + default route + curl -6 works\"\n else\n v6_state=\"path_broken\"\n v6_detail=\"addr present, default route present, but curl -6 fails — firewall or ISP black-holing\"\n fi\nfi\n\ncase \"$v6_state\" in\n disabled|healthy) pass \"IPv6 stack ($v6_state)\" \"$v6_detail\" ;;\n *) fail \"IPv6 stack ($v6_state)\" \"$v6_detail\" ;;\nesac\n\n# ---------------------------------------------------------------------------\nsection \"6. APPLICATION LAYER (real HTTP request)\"\n# ---------------------------------------------------------------------------\nfor url in \"https://www.google.com\" \"https://github.com\"; do\n if out=$(curl -sS -o /dev/null -w \"%{http_code} %{size_download}b\" --max-time \"$TIMEOUT\" \"$url\" 2>&1); then\n pass \"GET $url\" \"$out\"\n else\n fail \"GET $url\" \"$out\"\n fi\ndone\n\n# ---------------------------------------------------------------------------\nsection \"7. KNOWN VPN / DNS CLIENT FOOTPRINT\"\n# ---------------------------------------------------------------------------\n# Browser DoH state — Chrome / Brave / Edge / Firefox bypass system DNS when DoH set.\nbrowser_findings=\"\"\nfor label_prefs in \\\n \"Chrome:$HOME/.config/google-chrome/Default/Preferences\" \\\n \"Chromium:$HOME/.config/chromium/Default/Preferences\" \\\n \"Brave:$HOME/.config/BraveSoftware/Brave-Browser/Default/Preferences\" \\\n \"Edge:$HOME/.config/microsoft-edge/Default/Preferences\"; do\n label=\"${label_prefs%%:*}\"\n prefs=\"${label_prefs#*:}\"\n [[ -f \"$prefs\" ]] || continue\n mode=$(perl -ne 'if (/\"dns_over_https\"\\s*:\\s*\\{[^}]*\"mode\"\\s*:\\s*\"([^\"]+)\"/) { print \"$1\\n\"; exit }' \"$prefs\" 2>/dev/null)\n templates=$(perl -ne 'if (/\"dns_over_https\"\\s*:\\s*\\{[^}]*\"templates\"\\s*:\\s*\"([^\"]+)\"/) { print \"$1\\n\"; exit }' \"$prefs\" 2>/dev/null)\n if [[ -n \"$mode\" ]]; then\n browser_findings+=\" $label DoH: mode=$mode${templates:+, server=$templates}\\n\"\n else\n browser_findings+=\" $label installed, DoH: not configured (system DNS)\\n\"\n fi\ndone\nfor fx_prefs in \"$HOME/.mozilla/firefox\"/*.default*/prefs.js; do\n [[ -f \"$fx_prefs\" ]] || continue\n trr_mode=$(awk -F'\"' '/\"network.trr.mode\"/{print $4; exit}' \"$fx_prefs\" 2>/dev/null)\n trr_uri=$(awk -F'\"' '/\"network.trr.uri\"/{print $4; exit}' \"$fx_prefs\" 2>/dev/null)\n case \"${trr_mode:-0}\" in\n 2) state=\"enabled (with system fallback)\" ;;\n 3) state=\"enabled (no fallback)\" ;;\n 5) state=\"disabled by policy\" ;;\n *) state=\"off (system DNS)\" ;;\n esac\n browser_findings+=\" Firefox DoH: $state${trr_uri:+, server=$trr_uri}\\n\"\n break\ndone\nif [[ -n \"$browser_findings\" ]]; then\n info \" Browser DoH state (browsers may bypass system DNS):\"\n printf '%b' \"$browser_findings\"\nfi\n\nKNOWN=(\n /etc/openvpn /etc/wireguard /opt/cisco /etc/proton-vpn /etc/mullvad-vpn\n /opt/nordvpn /etc/NetworkManager/dnsmasq.d /etc/dnsmasq.d\n /etc/cloudflared /etc/nextdns.conf\n)\nfor p in \"${KNOWN[@]}\"; do\n [[ -e \"$p\" ]] && echo \" Found: $p\"\ndone\n\n# Running VPN / DNS proxy processes\necho \" VPN / DNS proxy processes:\"\npgrep -af 'openvpn|wireguard|wg-quick|mullvad|proton|nordvpn|cloudflared|nextdns|dnsmasq|stubby|dnscrypt' 2>/dev/null | head -10 | sed 's/^/ /' || true\n\n# ---------------------------------------------------------------------------\nsection \"8. ENVIRONMENT (WSL / container detection)\"\n# ---------------------------------------------------------------------------\nenv_type=\"\"\nif [[ -f /proc/sys/fs/binfmt_misc/WSLInterop ]] || grep -qi microsoft /proc/version 2>/dev/null; then\n env_type=\"WSL2\"\nelif [[ -f /.dockerenv ]]; then\n env_type=\"Docker container\"\nelif grep -qE 'docker|containerd|kubepods' /proc/1/cgroup 2>/dev/null; then\n env_type=\"container (cgroup signature)\"\nfi\n\nif [[ -z \"$env_type\" ]]; then\n info \" Bare-metal / VM Linux (no WSL/container signature)\"\nelse\n info \" Detected environment: $env_type\"\n case \"$env_type\" in\n WSL2*)\n info \" WSL2 has bespoke DNS handling. Key files if DNS misbehaves:\"\n info \" /etc/wsl.conf — controls generateResolvConf\"\n info \" /etc/resolv.conf — auto-generated by WSL unless wsl.conf opts out\"\n info \" Host Windows DNS — affects WSL DNS via mirrored mode\"\n info \" Fix pattern: edit /etc/wsl.conf, set [network] generateResolvConf=false, write static /etc/resolv.conf\"\n [[ -f /etc/wsl.conf ]] && { info \" --- /etc/wsl.conf ---\"; sed 's/^/ /' /etc/wsl.conf; }\n info \" --- /etc/resolv.conf head ---\"\n head -5 /etc/resolv.conf 2>/dev/null | sed 's/^/ /'\n ;;\n Docker*|container*)\n info \" Container DNS inherits from host or --dns flag at run time.\"\n info \" /etc/resolv.conf here is set by runtime, not user.\"\n info \" If broken inside container but fine on host: check 'docker network inspect' / runtime config.\"\n ;;\n esac\nfi\n\nemit_summary\nif [[ \"$JSON_MODE\" -eq 0 ]]; then\n if [[ -n \"$FIRST_FAIL\" ]]; then\n case \"$FIRST_FAIL\" in\n *\"LINK LAYER\"*) echo \" Next: check ip link / ip addr, DHCP, NetworkManager state\" ;;\n *\"SOCKET\"*) echo \" Next: check iptables/nftables OUTPUT chain; AV protocol filtering; consumer router DoH IP blocking\" ;;\n *\"ICMP\"*|*\"IP /\"*) echo \" Next: check ip route, ISP/upstream connectivity\" ;;\n *\"DNS INFRASTRUCTURE\"*) echo \" Next: check UDP/53 outbound, /etc/resolv.conf upstream\" ;;\n *\"RESOLVER PATH\"*) echo \" Next: bash scripts/linux/dns-audit.sh # drill rung 5 (the hook layer)\" ;;\n *\"APPLICATION\"*) echo \" Next: check http_proxy/https_proxy env, CA bundle, IPv6 preference\" ;;\n *) echo \" Next: re-run with --verbose; check references/common-culprits.md\" ;;\n esac\n fi\n echo\n echo \"=== END PROBE ===\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":14262,"content_sha256":"803a8d69d7b60131f5f7c77bb7dd08293e1d2dbe9f4c63b416a9b5eed4372115"},{"filename":"scripts/linux/resolved-reset.sh","content":"#!/usr/bin/env bash\n# net-ops :: linux/resolved-reset.sh\n# Reset systemd-resolved state when per-link DNS gets stuck (typical after\n# VPN disconnect leaves stale per-link DNS / domain settings).\n#\n# Defaults to DRY RUN — pass --apply to actually act.\n# Requires sudo for the apply path.\n\nset -eu\n\nAPPLY=0\nfor arg in \"$@\"; do\n case \"$arg\" in\n --apply) APPLY=1 ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [--apply]\n\nDiagnoses and (with --apply) resets systemd-resolved per-link DNS state.\n\n --apply Flush caches and revert each link's DNS to NetworkManager/networkd defaults\n (default: dry-run, prints what would happen)\nEOF\n exit 0 ;;\n esac\ndone\n\nif ! systemctl is-active systemd-resolved >/dev/null 2>&1; then\n echo \"systemd-resolved is not active. This script only applies when it is.\"\n echo \"On non-systemd-resolved systems, edit /etc/resolv.conf or NetworkManager config directly.\"\n exit 0\nfi\n\necho \"=== BEFORE ===\"\nresolvectl status 2>/dev/null | head -60\n\n# Find links with non-empty per-link DNS (potential stale state)\nLINKS_WITH_DNS=$(resolvectl status 2>/dev/null | awk '\n /^Link [0-9]+ \\(/{ split($0,a,\" \\\\(\"); split(a[2],b,\")\"); link=b[1]; ifn=a[1]; sub(\"Link \",\"\",ifn); has=0 }\n /Current DNS Server:|DNS Servers:/{ if(NF>3){print ifn\"|\"link} }\n' | sort -u)\n\nif [[ -z \"$LINKS_WITH_DNS\" ]]; then\n echo\n echo \"No links have explicit DNS set. Nothing to reset.\"\n exit 0\nfi\n\necho\necho \"=== LINKS WITH EXPLICIT DNS ===\"\necho \"$LINKS_WITH_DNS\" | while IFS='|' read -r idx name; do\n echo \" Link $idx ($name)\"\ndone\n\nif [[ \"$APPLY\" -eq 0 ]]; then\n echo\n echo \"DRY RUN — pass --apply to actually reset these links and flush caches.\"\n exit 0\nfi\n\nif [[ \"$EUID\" -ne 0 ]]; then\n echo \"Need root. Re-running with sudo...\"\n exec sudo \"$0\" --apply\nfi\n\necho\necho \"=== RESETTING ===\"\necho \"$LINKS_WITH_DNS\" | while IFS='|' read -r idx name; do\n if resolvectl revert \"$name\" 2>/dev/null; then\n echo \"[OK] reverted $name\"\n else\n echo \"[WARN] revert failed for $name (may be a VPN tunnel — manual cleanup may be needed)\"\n fi\ndone\n\necho\necho \"=== FLUSHING CACHE ===\"\nresolvectl flush-caches && echo \" cache flushed\"\n\n# Restart for good measure if user really wanted a reset\nsystemctl restart systemd-resolved\necho \" systemd-resolved restarted\"\n\necho\necho \"=== VERIFICATION ===\"\nif out=$(getent hosts google.com 2>&1) && [[ -n \"$out\" ]]; then\n echo \"[PASS] getent hosts google.com -> $(echo \"$out\" | awk '{print $1}')\"\nelse\n echo \"[FAIL] getent still failing. Check /etc/nsswitch.conf and /etc/resolv.conf.\"\nfi\n\nif curl -sS -o /dev/null -w \"[PASS] HTTPS google.com -> HTTP %{http_code}\\n\" --max-time 8 https://www.google.com 2>&1; then\n :\nelse\n echo \"[FAIL] HTTPS still broken.\"\nfi\n\necho\necho \"=== AFTER ===\"\nresolvectl status 2>/dev/null | head -40\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2875,"content_sha256":"c8d4cd4322ae3ca39f43de6e149be41fcee412ebe1bbf178112be892ed3c152f"},{"filename":"scripts/macos/dns-audit.sh","content":"#!/usr/bin/env bash\n# net-ops :: macos/dns-audit.sh\n# Deep DNS forensics for macOS. Use when probe.sh shows rung 4 (dig) PASS\n# but rung 5 (dscacheutil) FAIL — that signature points at a hook in the\n# macOS resolver chain.\n\nset -u\n\n# shellcheck source=../_lib/redact.sh\nsource \"$(dirname \"$0\")/../_lib/redact.sh\"\nparse_redact_flag \"$@\"\nmaybe_redact_self \"$@\"\n\necho \"=== scutil --dns (FULL) ===\"\nscutil --dns 2>/dev/null\n\necho\necho \"=== /etc/resolver/* (per-domain DNS overrides — VPN clients use these) ===\"\nif [[ -d /etc/resolver ]] && [[ -n \"$(ls -A /etc/resolver 2>/dev/null)\" ]]; then\n for f in /etc/resolver/*; do\n [[ -f \"$f\" ]] || continue\n echo \"--- $f ---\"\n echo \" modified: $(stat -f '%Sm' \"$f\" 2>/dev/null || stat -c '%y' \"$f\" 2>/dev/null)\"\n cat \"$f\" | sed 's/^/ /'\n done\nelse\n echo \"/etc/resolver/ empty or missing — no per-domain overrides.\"\nfi\n\necho\necho \"=== Configuration profiles with DNS settings ===\"\nprofiles list -type configuration 2>/dev/null | head -40\necho\necho \" (run 'sudo profiles show -type configuration' for full payloads)\"\n\necho\necho \"=== /etc/hosts (non-comment lines) ===\"\ngrep -vE '^\\s*(#|$)' /etc/hosts 2>/dev/null || echo \" (no custom entries)\"\n\necho\necho \"=== /etc/resolv.conf (legacy, usually a stub on macOS) ===\"\nif [[ -f /etc/resolv.conf ]]; then\n cat /etc/resolv.conf\nelse\n echo \" not present\"\nfi\n\necho\necho \"=== mDNSResponder state ===\"\nif pgrep -x mDNSResponder >/dev/null; then\n pid=$(pgrep -x mDNSResponder | head -1)\n echo \"PID: $pid\"\n ps -o pid,etime,command -p \"$pid\" 2>/dev/null\nfi\n\necho\necho \"=== Network services priority order ===\"\nnetworksetup -listnetworkserviceorder 2>/dev/null | head -30\n\necho\necho \"=== DNS servers per active service ===\"\nnetworksetup -listallnetworkservices 2>/dev/null | tail -n +2 | while read -r svc; do\n [[ \"$svc\" == \\** ]] && continue # disabled\n dns=$(networksetup -getdnsservers \"$svc\" 2>/dev/null)\n echo \" $svc: $dns\"\ndone\n\necho\necho \"=== Search domains per active service ===\"\nnetworksetup -listallnetworkservices 2>/dev/null | tail -n +2 | while read -r svc; do\n [[ \"$svc\" == \\** ]] && continue\n sd=$(networksetup -getsearchdomains \"$svc\" 2>/dev/null)\n echo \" $svc: $sd\"\ndone\n\necho\necho \"=== Third-party network kexts loaded ===\"\nkextstat 2>/dev/null | grep -iE 'cisco|anyconnect|proton|mullvad|nord|littlesnitch|lulu|nextdns|warp' || echo \" (none detected)\"\n\necho\necho \"=== ATTRIBUTION HINTS ===\"\n# Aggregate every nameserver we can see across all resolver surfaces, then\n# pattern-match each unique entry to a known VPN/DNS client signature.\nns_list=$( {\n [[ -d /etc/resolver ]] && grep -h '^nameserver' /etc/resolver/* 2>/dev/null | awk '{print $2}'\n scutil --dns 2>/dev/null | awk '/nameserver\\[[0-9]+\\]/{print $3}'\n networksetup -listallnetworkservices 2>/dev/null | tail -n +2 | while read -r svc; do\n [[ \"$svc\" == \\** ]] && continue\n networksetup -getdnsservers \"$svc\" 2>/dev/null | grep -E '^[0-9a-f:.]+

Network Operations Diagnose network problems on Windows, macOS, or Linux with a layered ladder that isolates faults to the smallest possible scope, then pattern-match against OS-specific culprits. Designed for the common case: someone reports "internet broken" on a box you can shell into (locally or via SSH). The Universal Insight Bypass-tool succeeds while OS-resolver fails is a smoking gun on every platform. It means DNS infrastructure is healthy but the operating system's name-resolution path is hooked or misconfigured. The bypass tool differs per OS but the discriminator is identical: | O…

|| true\n done\n} | sort -u | grep -v '^

Network Operations Diagnose network problems on Windows, macOS, or Linux with a layered ladder that isolates faults to the smallest possible scope, then pattern-match against OS-specific culprits. Designed for the common case: someone reports "internet broken" on a box you can shell into (locally or via SSH). The Universal Insight Bypass-tool succeeds while OS-resolver fails is a smoking gun on every platform. It means DNS infrastructure is healthy but the operating system's name-resolution path is hooked or misconfigured. The bypass tool differs per OS but the discriminator is identical: | O…

)\n\nif [[ -z \"$ns_list\" ]]; then\n echo \" (no nameservers found)\"\nfi\n\nwhile read -r n; do\n [[ -z \"$n\" ]] && continue\n case \"$n\" in\n 10.2.0.*) echo \" $n :: likely Proton VPN gateway\" ;;\n 10.64.0.*) echo \" $n :: likely Mullvad gateway\" ;;\n 10.211.*|10.212.*) echo \" $n :: likely Cisco AnyConnect\" ;;\n 10.5.0.*) echo \" $n :: likely NordVPN gateway\" ;;\n 100.100.100.100) echo \" $n :: Tailscale MagicDNS (expected)\" ;;\n 127.0.0.1|127.0.0.2|::1) echo \" $n :: local DNS proxy (NextDNS, AdGuard, dnsmasq, etc.)\" ;;\n 1.1.1.1|1.0.0.1) echo \" $n :: Cloudflare public DNS\" ;;\n 8.8.8.8|8.8.4.4) echo \" $n :: Google public DNS\" ;;\n 9.9.9.9|149.112.112.112) echo \" $n :: Quad9 public DNS\" ;;\n esac\ndone \u003c\u003c\u003c \"$ns_list\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3859,"content_sha256":"b26b703bc7f884b37f074b4d1b2e76613b5c2bc48597967ffbcfbce1a9d7a4cd"},{"filename":"scripts/macos/probe.sh","content":"#!/usr/bin/env bash\n# net-ops :: macos/probe.sh\n# Full layered diagnostic ladder for macOS network troubleshooting.\n# Outputs structured [PASS]/[FAIL] lines so a human or LLM can scan for\n# the first FAIL and drill in.\n\nset -u\n\nTEST_HOST=\"${TEST_HOST:-google.com}\"\nTEST_IPS=(\"1.1.1.1\" \"8.8.8.8\")\nTIMEOUT=\"${TIMEOUT:-5}\"\n\nVERBOSE=0\nfor arg in \"$@\"; do\n case \"$arg\" in\n --verbose|-v) VERBOSE=1 ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [--redact] [--verbose] [--json] [--quick]\n\n --redact Mask private IPs, MAC addresses, and *.ts.net tailnet names\n --verbose Full scutil --dns dump (default: condensed one-line-per-resolver)\n --json Newline-delimited JSON output (for piping to jq, dashboards)\n --quick Skip rungs 1-4 and 7 if the last full run cached as healthy\n (cache: \\${TMPDIR}/net-ops/last-state.json, TTL 10min)\n\nCompose freely: --json + --redact emits sanitized NDJSON.\nEOF\n exit 0 ;;\n esac\ndone\n\n# shellcheck source=../_lib/redact.sh\nsource \"$(dirname \"$0\")/../_lib/redact.sh\"\n# shellcheck source=../_lib/output.sh\nsource \"$(dirname \"$0\")/../_lib/output.sh\"\n# shellcheck source=../_lib/cache.sh\nsource \"$(dirname \"$0\")/../_lib/cache.sh\"\nparse_redact_flag \"$@\"\nparse_output_flags \"$@\"\nparse_quick_flag \"$@\"\nmaybe_redact_self \"$@\"\n\nif cache_indicates_healthy; then\n info \" [--quick: last full run was healthy, skipping rungs 1-4 and 7]\"\nfi\n\n# ---------------------------------------------------------------------------\nif should_run_rung 1; then\nsection \"1. LINK LAYER\"\n# ---------------------------------------------------------------------------\nACTIVE_IFS=$(networksetup -listallhardwareports 2>/dev/null | awk '/Hardware Port/{port=$3} /Device/{print port\" \"$2}' || true)\necho \"$ACTIVE_IFS\" | while read -r line; do\n [[ -z \"$line\" ]] && continue\n name=\"${line% *}\"; dev=\"${line##* }\"\n status=$(ifconfig \"$dev\" 2>/dev/null | awk '/status:/{print $2; exit}')\n if [[ \"$status\" == \"active\" ]]; then\n ip=$(ifconfig \"$dev\" 2>/dev/null | awk '/inet /{print $2; exit}')\n pass \"Interface $name ($dev) active\" \"$ip\"\n fi\ndone\n\nGATEWAY=$(route -n get default 2>/dev/null | awk '/gateway:/{print $2}')\nDEFAULT_IF=$(route -n get default 2>/dev/null | awk '/interface:/{print $2}')\n[[ -n \"$GATEWAY\" ]] && pass \"Default gateway\" \"$GATEWAY via $DEFAULT_IF\" || fail \"Default gateway\" \"none configured\"\n\nfi # end rung 1\n\n# ---------------------------------------------------------------------------\nif should_run_rung 2; then\nsection \"2. IP / ICMP REACHABILITY\"\n# ---------------------------------------------------------------------------\n[[ -n \"${GATEWAY:-}\" ]] && {\n if ping -c 2 -W \"${TIMEOUT}000\" \"$GATEWAY\" >/dev/null 2>&1; then pass \"Ping gateway $GATEWAY\"; else fail \"Ping gateway $GATEWAY\"; fi\n}\nfor ip in \"${TEST_IPS[@]}\"; do\n if ping -c 2 -W \"${TIMEOUT}000\" \"$ip\" >/dev/null 2>&1; then pass \"Ping $ip\"; else fail \"Ping $ip\"; fi\ndone\n\nfi # end rung 2\n\n# ---------------------------------------------------------------------------\nif should_run_rung 3; then\nsection \"3. TCP/UDP SOCKET REACHABILITY\"\n# ---------------------------------------------------------------------------\nfor ip in \"${TEST_IPS[@]}\"; do\n if nc -zv -G \"$TIMEOUT\" \"$ip\" 443 >/dev/null 2>&1; then pass \"TCP/443 -> $ip\"; else fail \"TCP/443 -> $ip\"; fi\n if nc -zv -G \"$TIMEOUT\" \"$ip\" 53 >/dev/null 2>&1; then pass \"TCP/53 -> $ip\"; else fail \"TCP/53 -> $ip\"; fi\ndone\n\n# Raw UDP/53 — uses dig with explicit server, bypasses /etc/resolv.conf\nfor ip in \"${TEST_IPS[@]}\"; do\n if dig +short +time=\"$TIMEOUT\" +tries=1 @\"$ip\" \"$TEST_HOST\" >/dev/null 2>&1; then\n result=$(dig +short +time=\"$TIMEOUT\" +tries=1 @\"$ip\" \"$TEST_HOST\" | head -1)\n pass \"UDP/53 -> $ip (dig)\" \"$result\"\n else\n fail \"UDP/53 -> $ip (dig)\"\n fi\ndone\n\nfi # end rung 3\n\n# ---------------------------------------------------------------------------\nif should_run_rung 4; then\nsection \"4. DNS INFRASTRUCTURE (bypass tools)\"\n# ---------------------------------------------------------------------------\n# dig uses its own resolver — does NOT touch macOS DNS resolution chain\nfor srv in \"\" \"${TEST_IPS[@]}\"; do\n if [[ -z \"$srv\" ]]; then\n out=$(dig +short +time=\"$TIMEOUT\" +tries=1 \"$TEST_HOST\" 2>&1)\n label=\"default\"\n else\n out=$(dig +short +time=\"$TIMEOUT\" +tries=1 @\"$srv\" \"$TEST_HOST\" 2>&1)\n label=\"$srv\"\n fi\n if [[ -n \"$out\" && ! \"$out\" =~ \"timed out\"|\"connection refused\" ]]; then\n pass \"dig via $label\" \"$(echo \"$out\" | head -1)\"\n else\n fail \"dig via $label\" \"$out\"\n fi\ndone\n\nfi # end rung 4\n\n# ---------------------------------------------------------------------------\nsection \"5. macOS RESOLVER PATH (the hook layer)\"\n# ---------------------------------------------------------------------------\n# dscacheutil uses the macOS resolver chain — goes through everything\nout=$(dscacheutil -q host -a name \"$TEST_HOST\" 2>&1)\nif echo \"$out\" | grep -q \"ip_address:\"; then\n addr=$(echo \"$out\" | awk '/ip_address:/{print $2; exit}')\n pass \"dscacheutil (system resolver)\" \"$addr\"\nelse\n fail \"dscacheutil (system resolver)\" \"$(echo \"$out\" | head -3)\"\nfi\n\n# /etc/resolver/* — per-domain overrides, classic VPN residue\nif [[ -d /etc/resolver ]]; then\n resolver_files=$(ls /etc/resolver/ 2>/dev/null)\n if [[ -n \"$resolver_files\" ]]; then\n echo \" /etc/resolver/ contents (per-domain DNS overrides):\"\n for f in /etc/resolver/*; do\n [[ -f \"$f\" ]] || continue\n domain=\"${f##*/}\"\n ns=$(awk '/^nameserver/{print $2}' \"$f\" | tr '\\n' ' ')\n echo \" $domain -> $ns\"\n done\n fi\nfi\n\n# scutil DNS state — the authoritative view of macOS resolver config\nif [[ \"$VERBOSE\" -eq 1 ]]; then\n echo \" scutil --dns (full):\"\n scutil --dns 2>/dev/null | sed 's/^/ /'\nelse\n # Condensed: one line per resolver — scope (via domain or search), nameservers, order\n echo \" scutil --dns (condensed, --verbose for full):\"\n scutil --dns 2>/dev/null | awk '\n /^resolver #/{ if(num){flush()} num=$2; sub(/#/,\"\",num); scope=\"\"; ns=\"\"; ord=\"\" }\n /search domain\\[0\\]/{ scope=\"search=\"$NF }\n /domain[[:space:]]*:/{ scope=\"domain=\"$NF }\n /options/{ if($NF~/mdns/) scope=\"mdns\" }\n /nameserver\\[[0-9]+\\]/{ ns=ns?ns\",\"$NF:$NF }\n /order[[:space:]]*:/{ ord=$NF }\n function flush() {\n if (!scope) scope=\"default\"\n print \" #\"num\" scope=\"scope\" via=\"ns\" order=\"ord\n }\n END{ if(num) flush() }\n '\nfi\n\n# Configuration profiles (MDM / VPN-installed). Without sudo we only see user-scope.\nprofile_count=$(profiles list -type configuration 2>/dev/null | grep -c \"attribute:\" 2>/dev/null)\nprofile_count=\"${profile_count:-0}\"\nif [[ \"$profile_count\" =~ ^[0-9]+$ ]] && (( profile_count > 0 )); then\n echo \" Configuration profiles installed (user scope): $profile_count\"\n echo \" For full detail incl. system profiles: sudo profiles list -type configuration\"\nfi\n\n# Local DNS proxy detection — derived from scutil (works unprivileged).\n# Common with NextDNS, AdGuard, dnsmasq, Pi-hole client, Cloudflare WARP.\nif scutil --dns 2>/dev/null | awk '/nameserver\\[[0-9]+\\]/{print $3}' | grep -qE '^(127\\.|::1$)'; then\n echo \" !! Local DNS proxy detected in resolver chain (127.x or ::1 nameserver)\"\n echo \" Apps using the system resolver may route DNS through it.\"\n echo \" For PID/process: sudo lsof -nP -iUDP:53\"\nfi\n\n# mDNSResponder state\nif pgrep -x mDNSResponder >/dev/null; then\n pid=$(pgrep -x mDNSResponder | head -1)\n pass \"mDNSResponder running\" \"PID $pid\"\nelse\n fail \"mDNSResponder\" \"not running — system DNS will be broken\"\nfi\n\n# ---------------------------------------------------------------------------\n# Time-sync deep-dive: compare local clock to HTTP Date, AND check whether\n# macOS network time sync itself is enabled + which server it's pointing at.\n# Stratum-16 (unsynced) clocks are the silent killer of TLS validation.\nntp_enabled=$(systemsetup -getusingnetworktime 2>/dev/null | awk -F': ' '{print $2}')\nntp_server=$(systemsetup -getnetworktimeserver 2>/dev/null | awk -F': ' '{print $2}')\n\n# HTTP Date drift (works without elevated privs, no NTP infra needed)\nremote_date=$(curl -sIA 'net-ops-probe' --max-time 5 https://www.google.com 2>/dev/null | awk -F': ' 'tolower($1)==\"date\"{print $2; exit}' | tr -d '\\r')\ndrift_ok=1\ndrift_detail=\"\"\nif [[ -n \"$remote_date\" ]]; then\n remote_epoch=$(date -j -f '%a, %d %b %Y %H:%M:%S %Z' \"$remote_date\" +%s 2>/dev/null)\n if [[ -n \"$remote_epoch\" ]]; then\n local_epoch=$(date +%s)\n drift=$(( local_epoch - remote_epoch ))\n abs_drift=${drift#-}\n if [[ \"$abs_drift\" -lt 300 ]]; then\n drift_detail=\"${drift}s vs HTTP Date (within ±5min)\"\n else\n drift_ok=0\n drift_detail=\"${drift}s drift — will break TLS cert validation\"\n fi\n fi\nfi\n\n# Optional: query the configured NTP server for actual stratum / offset.\n# sntp is built-in on macOS; suppress its noisy output.\nntp_offset=\"\"\nif [[ -n \"$ntp_server\" ]] && command -v sntp >/dev/null 2>&1; then\n ntp_offset=$(sntp -t 3 \"$ntp_server\" 2>/dev/null | awk '/[+-][0-9]+\\.[0-9]+/{print $1; exit}')\nfi\n\ncombined=\"$drift_detail\"\n[[ -n \"$ntp_enabled\" ]] && combined=\"$combined; NTP sync=$ntp_enabled\"\n[[ -n \"$ntp_server\" ]] && combined=\"$combined; server=$ntp_server\"\n[[ -n \"$ntp_offset\" ]] && combined=\"$combined; sntp offset=${ntp_offset}s\"\n\nif [[ \"$drift_ok\" -eq 1 ]] && { [[ \"$ntp_enabled\" == \"On\" ]] || [[ -z \"$ntp_enabled\" ]]; }; then\n pass \"Time sync\" \"$combined\"\nelse\n fail \"Time sync\" \"$combined\"\nfi\n\n# MTU / path-MTU discovery test. Standard Ethernet MTU is 1500.\n# We send a 1472-byte payload (1472 + 20 IP + 8 ICMP = 1500) with DF set.\n# If this fails but a smaller size works, there's a path-MTU issue\n# (PPPoE, weird tunnel, broken ICMP \"fragmentation needed\" delivery).\nif ping -D -s 1472 -c 1 -t 3 1.1.1.1 >/dev/null 2>&1; then\n pass \"Path MTU 1500 (1472-byte DF payload)\" \"to 1.1.1.1\"\nelse\n if ping -D -s 1400 -c 1 -t 3 1.1.1.1 >/dev/null 2>&1; then\n fail \"Path MTU 1500 (1472-byte DF payload)\" \"1500 fails, 1428+ works — path MTU \u003c 1500 (VPN/PPPoE?)\"\n else\n # Both fail — DF blocking entirely; don't flag as MTU\n pass \"Path MTU test inconclusive\" \"ICMP DF blocked or destination unreachable\"\n fi\nfi\n\n# IPv6 deep-dive — classifies v6 stack state across four meaningful tiers\n# instead of a binary works/broken. Each tier maps to a distinct fix path.\nv6_state=\"\"\nv6_detail=\"\"\n\n# 1. Any v6 address on a non-loopback interface?\nv6_addrs=$(ifconfig 2>/dev/null | awk '/^[a-z]/{ifn=$1} /inet6 /{print ifn\" \"$2}' | grep -v \"::1\\|fe80::\" | grep -v \"^utun\\|^awdl\\|^llw\\|^bridge\")\n# 2. Any GLOBAL v6 address (not ULA fd00::/8)?\nv6_global=$(printf '%s\\n' \"$v6_addrs\" | awk '$2 !~ /^fd/ && $2 !~ /^fc/{print; exit}')\n# 3. Is there an actual global default route?\nv6_default=$(route -n get -inet6 default 2>&1 | awk '/gateway:/{print $2; exit}')\n[[ \"$v6_default\" =~ ^fe80 ]] && v6_default=\"\" # link-local doesn't count\n\nif [[ -z \"$v6_addrs\" ]]; then\n v6_state=\"disabled\"\n v6_detail=\"no v6 addresses on physical interfaces — IPv6 disabled or unconfigured\"\nelif [[ -z \"$v6_global\" ]]; then\n v6_state=\"ula_only\"\n v6_detail=\"only ULA (fd00::/8) addresses present — ISP/router not delegating public v6 prefix\"\nelif [[ -z \"$v6_default\" ]]; then\n v6_state=\"no_route\"\n v6_detail=\"global v6 address present but no default route — RA not received or NDP broken\"\nelse\n # We have a v6 address and a route — test actual connectivity\n aaaa=$(dig +short +time=2 +tries=1 AAAA \"$TEST_HOST\" 2>/dev/null | head -1)\n if [[ -n \"$aaaa\" ]] && curl -6 -sS -o /dev/null --max-time 4 \"https://$TEST_HOST\" 2>/dev/null; then\n v6_state=\"healthy\"\n v6_detail=\"global addr + default route + curl -6 works\"\n else\n v6_state=\"path_broken\"\n v6_detail=\"addr=$v6_global, route via $v6_default, but curl -6 fails — upstream v6 path dead\"\n fi\nfi\n\ncase \"$v6_state\" in\n disabled|healthy)\n pass \"IPv6 stack ($v6_state)\" \"$v6_detail\" ;;\n ula_only)\n fail \"IPv6 stack ($v6_state)\" \"$v6_detail — apps may try v6 first, hit 'no route', fall back to v4 (slow). Fix: sudo networksetup -setv6off \u003cservice>\" ;;\n no_route)\n fail \"IPv6 stack ($v6_state)\" \"$v6_detail — check ndp -an for RA receipt; restart interface or check router RA config\" ;;\n path_broken)\n fail \"IPv6 stack ($v6_state)\" \"$v6_detail — VPN/firewall blocking v6, or ISP black-holing v6 traffic\" ;;\nesac\n\n# ---------------------------------------------------------------------------\nsection \"6. APPLICATION LAYER (real HTTP request)\"\n# ---------------------------------------------------------------------------\nfor url in \"https://www.google.com\" \"https://github.com\"; do\n if out=$(curl -sS -o /dev/null -w \"%{http_code} %{size_download}b\" --max-time \"$TIMEOUT\" \"$url\" 2>&1); then\n pass \"GET $url\" \"$out\"\n else\n fail \"GET $url\" \"$out\"\n fi\ndone\n\n# ---------------------------------------------------------------------------\nif should_run_rung 7; then\nsection \"7. KNOWN VPN / DNS CLIENT FOOTPRINT\"\n# ---------------------------------------------------------------------------\nKNOWN_PATHS=(\n \"/Applications/Proton VPN.app\"\n \"/Applications/Mullvad VPN.app\"\n \"/Applications/Tailscale.app\"\n \"/Applications/Cisco/Cisco Secure Client.app\"\n \"/Applications/Cisco/Cisco AnyConnect Secure Mobility Client.app\"\n \"/Applications/NordVPN.app\"\n \"/Applications/NextDNS.app\"\n \"/Applications/Little Snitch.app\"\n \"/Applications/Lulu.app\"\n \"/Library/Application Support/NextDNS\"\n)\nfor p in \"${KNOWN_PATHS[@]}\"; do\n [[ -e \"$p\" ]] && echo \" Installed: $p\"\ndone\n\n# Browser DoH state — Chrome / Brave / Edge / Firefox have their own resolvers\n# that bypass system DNS entirely when DoH is configured. Useful for explaining\n# \"Chrome works but Safari doesn't\" type asymmetries.\nbrowser_findings=\"\"\nchrome_prefs=\"$HOME/Library/Application Support/Google/Chrome/Default/Preferences\"\nbrave_prefs=\"$HOME/Library/Application Support/BraveSoftware/Brave-Browser/Default/Preferences\"\nedge_prefs=\"$HOME/Library/Application Support/Microsoft Edge/Default/Preferences\"\nfor label_prefs in \"Chrome:$chrome_prefs\" \"Brave:$brave_prefs\" \"Edge:$edge_prefs\"; do\n label=\"${label_prefs%%:*}\"\n prefs=\"${label_prefs#*:}\"\n if [[ -f \"$prefs\" ]]; then\n # Chromium stores DoH mode under dns_over_https.mode: \"off\" | \"automatic\" | \"secure\"\n mode=$(perl -ne 'if (/\"dns_over_https\"\\s*:\\s*\\{[^}]*\"mode\"\\s*:\\s*\"([^\"]+)\"/) { print \"$1\\n\"; exit }' \"$prefs\" 2>/dev/null)\n templates=$(perl -ne 'if (/\"dns_over_https\"\\s*:\\s*\\{[^}]*\"templates\"\\s*:\\s*\"([^\"]+)\"/) { print \"$1\\n\"; exit }' \"$prefs\" 2>/dev/null)\n if [[ -n \"$mode\" ]]; then\n browser_findings+=\" $label DoH: mode=$mode${templates:+, server=$templates}\\n\"\n else\n browser_findings+=\" $label installed, DoH: not configured (system DNS)\\n\"\n fi\n fi\ndone\n# Firefox: per-profile prefs.js, network.trr.mode (0=off, 2=enabled w/fallback, 3=enabled only, 5=disabled)\nfor fx_prefs in \"$HOME/Library/Application Support/Firefox/Profiles\"/*.default*/prefs.js; do\n [[ -f \"$fx_prefs\" ]] || continue\n trr_mode=$(awk -F'\"' '/\"network.trr.mode\"/{print $4; exit}' \"$fx_prefs\" 2>/dev/null)\n trr_uri=$(awk -F'\"' '/\"network.trr.uri\"/{print $4; exit}' \"$fx_prefs\" 2>/dev/null)\n case \"${trr_mode:-0}\" in\n 2) state=\"enabled (with system fallback)\" ;;\n 3) state=\"enabled (no fallback)\" ;;\n 5) state=\"disabled by policy\" ;;\n *) state=\"off (system DNS)\" ;;\n esac\n browser_findings+=\" Firefox DoH: $state${trr_uri:+, server=$trr_uri}\\n\"\n break # only check one profile\ndone\nif [[ -n \"$browser_findings\" ]]; then\n info \" Browser DoH state (browsers may bypass system DNS):\"\n printf '%b' \"$browser_findings\"\nfi\n\n# Network services often reveal VPN/DNS clients that don't install at /Applications\n# (e.g. CLI-only NextDNS, kernel/system extensions, virtual interfaces)\nns_pattern='Proton|Mullvad|NextDNS|Cisco|NordVPN|Tailscale|WireGuard|OpenVPN|Cloudflare|WARP|AdGuard'\nns_found=$(networksetup -listallnetworkservices 2>/dev/null | grep -iE \"$ns_pattern\" || true)\nif [[ -n \"$ns_found\" ]]; then\n echo \" Network services:\"\n echo \"$ns_found\" | sed 's/^/ /'\nfi\n\nfi # end rung 7\n\n# Persist state for future --quick runs (only when we ran the FULL ladder).\nif [[ \"$QUICK_MODE\" -eq 0 ]]; then\n cache_save_state \"$PASS_COUNT\" \"$FAIL_COUNT\" \"$FIRST_FAIL\"\nfi\n\nemit_summary\nif [[ \"$JSON_MODE\" -eq 0 ]]; then\n if [[ -n \"$FIRST_FAIL\" ]]; then\n case \"$FIRST_FAIL\" in\n *\"LINK LAYER\"*) echo \" Next: check ifconfig / networksetup, fix interface / DHCP\" ;;\n *\"SOCKET\"*) echo \" Next: check Little Snitch / Lulu / pfctl rules; AV protocol filtering; consumer router DoH IP blocking\" ;;\n *\"ICMP\"*|*\"IP /\"*) echo \" Next: check route table, ISP/upstream connectivity\" ;;\n *\"DNS INFRASTRUCTURE\"*) echo \" Next: check UDP/53 outbound, router DNS forwarder\" ;;\n *\"RESOLVER PATH\"*) echo \" Next: bash scripts/macos/dns-audit.sh # drill rung 5 (the hook layer)\" ;;\n *\"APPLICATION\"*) echo \" Next: check proxy (scutil --proxy), keychain certs, IPv6 preference\" ;;\n *) echo \" Next: re-run with --verbose; check references/common-culprits.md\" ;;\n esac\n else\n echo \" (No failures. If user still reports issues, see rung 7 footprint and time-based notes in references/diagnostic-ladder.md.)\"\n fi\n echo\n echo \"=== END PROBE ===\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":17785,"content_sha256":"9f2a25348796cf257aed0606abbac6dc15ea160dc1ce766f9e720ee8e7a8bf09"},{"filename":"scripts/macos/resolver-clean.sh","content":"#!/usr/bin/env bash\n# net-ops :: macos/resolver-clean.sh\n# Safely remove orphaned /etc/resolver/* files left behind by disconnected VPNs.\n# NEVER removes Tailscale or current-VPN-tunnel entries.\n#\n# Defaults to DRY RUN — pass --apply to actually delete.\n# Requires sudo.\n\nset -eu\n\nAPPLY=0\nPROTECT_PATTERNS=\"${PROTECT_PATTERNS:-100\\.100\\.100\\.100}\"\n\nfor arg in \"$@\"; do\n case \"$arg\" in\n --apply) APPLY=1 ;;\n --protect=*) PROTECT_PATTERNS=\"${arg#--protect=}\" ;;\n --help|-h)\n cat \u003c\u003cEOF\nUsage: $0 [--apply] [--protect=REGEX]\n\n --apply Actually delete (default: dry-run only)\n --protect=REGEX Nameserver pattern to protect (default: Tailscale's 100.100.100.100)\n\nExamples:\n $0 # show what would be removed\n $0 --apply # remove orphan resolvers, protecting Tailscale\n $0 --apply --protect='100\\\\.\\\\.|192\\\\.168\\\\.1\\\\.' # also protect 192.168.1.x\nEOF\n exit 0 ;;\n esac\ndone\n\nif [[ ! -d /etc/resolver ]] || [[ -z \"$(ls -A /etc/resolver 2>/dev/null)\" ]]; then\n echo \"/etc/resolver/ is empty. Nothing to do.\"\n exit 0\nfi\n\necho \"=== BEFORE ===\"\nfor f in /etc/resolver/*; do\n [[ -f \"$f\" ]] || continue\n ns=$(awk '/^nameserver/{print $2}' \"$f\" | tr '\\n' ',')\n echo \" $f -> ${ns%,}\"\ndone\n\nTARGETS=()\nfor f in /etc/resolver/*; do\n [[ -f \"$f\" ]] || continue\n if awk '/^nameserver/{print $2}' \"$f\" | grep -qE \"$PROTECT_PATTERNS\"; then\n continue\n fi\n TARGETS+=(\"$f\")\ndone\n\nif [[ \"${#TARGETS[@]}\" -eq 0 ]]; then\n echo\n echo \"No orphan resolver files (all match protected nameserver pattern). Nothing to clean.\"\n exit 0\nfi\n\necho\necho \"=== TARGETS FOR REMOVAL ===\"\nfor f in \"${TARGETS[@]}\"; do\n echo \" $f\"\ndone\n\nif [[ \"$APPLY\" -eq 0 ]]; then\n echo\n echo \"DRY RUN — pass --apply to actually remove the files above.\"\n exit 0\nfi\n\n# Apply\nif [[ \"$EUID\" -ne 0 ]]; then\n echo \"Need root. Re-running with sudo...\"\n exec sudo \"$0\" --apply --protect=\"$PROTECT_PATTERNS\"\nfi\n\necho\necho \"=== REMOVING ===\"\nfor f in \"${TARGETS[@]}\"; do\n if rm -f \"$f\"; then\n echo \"[OK] $f\"\n else\n echo \"[FAIL] $f\"\n fi\ndone\n\necho\necho \"=== FLUSHING DNS CACHE ===\"\ndscacheutil -flushcache\nkillall -HUP mDNSResponder 2>/dev/null || true\necho \" done.\"\n\necho\necho \"=== VERIFICATION ===\"\nif out=$(dscacheutil -q host -a name google.com 2>&1) && echo \"$out\" | grep -q \"ip_address:\"; then\n addr=$(echo \"$out\" | awk '/ip_address:/{print $2; exit}')\n echo \"[PASS] dscacheutil google.com -> $addr\"\nelse\n echo \"[FAIL] dscacheutil still broken. Drill into scutil --dns and configuration profiles.\"\nfi\n\nif curl -sS -o /dev/null -w \"[PASS] HTTPS google.com -> HTTP %{http_code}\\n\" --max-time 8 https://www.google.com 2>&1; then\n :\nelse\n echo \"[FAIL] HTTPS still broken.\"\nfi\n\necho\necho \"=== AFTER ===\"\nif [[ -n \"$(ls -A /etc/resolver 2>/dev/null)\" ]]; then\n for f in /etc/resolver/*; do\n [[ -f \"$f\" ]] || continue\n ns=$(awk '/^nameserver/{print $2}' \"$f\" | tr '\\n' ',')\n echo \" $f -> ${ns%,}\"\n done\nelse\n echo \" /etc/resolver/ is now empty.\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3146,"content_sha256":"e5e06c720997a26aea1cab43d29c5322a6ae7dde2bda0d02e50e148f89d41b03"},{"filename":"scripts/probe","content":"#!/usr/bin/env bash\n# net-ops :: probe (dispatcher)\n# Detects the local OS and runs the matching per-OS probe with the same args.\n# Supports --watch=N for continuous monitoring (prints state on change only).\n# For Windows targets you reach over SSH, invoke scripts/windows/probe.ps1\n# directly via PowerShell (see scripts/ssh-bootstrap.sh for the pattern).\n\nset -eu\n\nhere=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\n\n# Parse --watch (with optional =N seconds; default 30)\nwatch_interval=0\nclean_args=()\nfor arg in \"$@\"; do\n case \"$arg\" in\n --watch) watch_interval=30 ;;\n --watch=*) watch_interval=\"${arg#--watch=}\" ;;\n *) clean_args+=(\"$arg\") ;;\n esac\ndone\n\n# Resolve the per-OS probe script\ncase \"$(uname -s 2>/dev/null)\" in\n Darwin) target=\"$here/macos/probe.sh\" ;;\n Linux) target=\"$here/linux/probe.sh\" ;;\n CYGWIN*|MINGW*|MSYS*)\n cat >&2 \u003c\u003cEOF\nDetected Windows shell environment ($(uname -s)).\nRun the PowerShell probe directly:\n powershell -NoProfile -File \"$here/windows/probe.ps1\" $*\nEOF\n exit 2 ;;\n *)\n echo \"net-ops: unsupported OS '$(uname -s 2>/dev/null)'\" >&2\n exit 2 ;;\nesac\n\n# Continuous watch mode: re-run every N seconds, but only print on state change.\n# A \"state\" is the (pass_count, fail_count, first_fail) tuple.\nif [[ \"$watch_interval\" -gt 0 ]]; then\n echo \"Watching every ${watch_interval}s — prints on state change only. Ctrl-C to stop.\" >&2\n last_state=\"\"\n while true; do\n # Run in JSON mode to extract summary cleanly; suppress streaming output\n json=$(bash \"$target\" --json ${clean_args[@]+\"${clean_args[@]}\"} 2>/dev/null | grep '^{\"type\":\"summary\"' | head -1)\n state=$(printf '%s' \"$json\" | tr -d ' ')\n ts=$(date '+%Y-%m-%d %H:%M:%S')\n if [[ \"$state\" != \"$last_state\" ]]; then\n if [[ -z \"$last_state\" ]]; then\n printf '[%s] initial state: %s\\n' \"$ts\" \"$json\"\n else\n printf '[%s] CHANGED: %s\\n' \"$ts\" \"$json\"\n fi\n last_state=\"$state\"\n else\n printf '[%s] (no change)\\n' \"$ts\"\n fi\n sleep \"$watch_interval\"\n done\nfi\n\n# Default: one-shot execution\nexec \"$target\" ${clean_args[@]+\"${clean_args[@]}\"}\n","content_type":"text/plain; charset=utf-8","language":null,"size":2258,"content_sha256":"5852fb3066e107b599811e6b81f71a1e200986bebba65375e10e0419efa5ca89"},{"filename":"scripts/reverse-probe.sh","content":"#!/usr/bin/env bash\n# net-ops :: reverse-probe.sh\n# Diagnose a TARGET host from OUTSIDE — useful when the local probe on the\n# target says \"all good\" but external services / users still report problems.\n# Runs from this machine against a target host you can reach (LAN, tailnet,\n# public IP, etc).\n#\n# Usage:\n# scripts/reverse-probe.sh \u003chost> # use default ports/checks\n# scripts/reverse-probe.sh \u003chost> [port...] # add custom TCP ports to probe\n#\n# Examples:\n# scripts/reverse-probe.sh example.local\n# scripts/reverse-probe.sh 100.84.X.X 8080 5432\n# scripts/reverse-probe.sh api.mycompany.com 443\n\nset -u\n\nTARGET=\"${1:-}\"\nif [[ -z \"$TARGET\" ]]; then\n echo \"Usage: $0 \u003chost> [extra_tcp_port ...]\" >&2\n exit 1\nfi\nshift\nEXTRA_PORTS=(\"$@\")\nDEFAULT_PORTS=(22 80 443)\nTIMEOUT=4\n\n# shellcheck source=_lib/redact.sh\nsource \"$(dirname \"$0\")/_lib/redact.sh\"\n# shellcheck source=_lib/output.sh\nsource \"$(dirname \"$0\")/_lib/output.sh\"\nparse_redact_flag \"$@\"\nparse_output_flags \"$@\"\nmaybe_redact_self \"$TARGET\" \"$@\"\n\n# Resolve target — separates DNS issues from reachability issues\nsection \"1. NAME RESOLUTION FROM HERE\"\nif [[ \"$TARGET\" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n pass \"Target is literal IP\" \"$TARGET\"\n TARGET_IP=\"$TARGET\"\nelse\n resolved=$(dig +short +time=3 +tries=1 \"$TARGET\" 2>/dev/null | head -1)\n if [[ -n \"$resolved\" ]]; then\n pass \"Resolved $TARGET (dig, bypass resolver)\" \"$resolved\"\n TARGET_IP=\"$resolved\"\n else\n fail \"Resolved $TARGET\" \"no answer from local DNS — can't proceed past name layer\"\n emit_summary\n exit 1\n fi\nfi\n\nsection \"2. ICMP REACHABILITY\"\nif ping -c 2 -W $((TIMEOUT * 1000)) \"$TARGET_IP\" >/dev/null 2>&1; then\n pass \"Ping $TARGET_IP\"\nelse\n fail \"Ping $TARGET_IP\" \"no ICMP response (or ICMP filtered)\"\nfi\n\nsection \"3. TCP PORT REACHABILITY\"\n# De-duplicate ports — extras may overlap defaults\nall_ports=$(printf '%s\\n' \"${DEFAULT_PORTS[@]}\" ${EXTRA_PORTS[@]+\"${EXTRA_PORTS[@]}\"} | awk '!seen[$0]++')\nwhile read -r port; do\n [[ -z \"$port\" ]] && continue\n if nc -zv -G \"$TIMEOUT\" \"$TARGET_IP\" \"$port\" >/dev/null 2>&1; then\n pass \"TCP/$port -> $TARGET_IP\" \"open\"\n else\n fail \"TCP/$port -> $TARGET_IP\" \"closed or filtered\"\n fi\ndone \u003c\u003c\u003c \"$all_ports\"\n\nsection \"4. TLS / HTTPS HEALTH (if 443 open)\"\nif nc -zv -G \"$TIMEOUT\" \"$TARGET_IP\" 443 >/dev/null 2>&1; then\n if [[ \"$TARGET\" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n # Connect by IP; cert check will fail SNI but we can still probe\n out=$(curl -sS -o /dev/null -w \"%{http_code}|%{time_total}\" --max-time \"$TIMEOUT\" -k \"https://$TARGET_IP\" 2>&1)\n pass \"HTTPS to IP (cert SNI may not match)\" \"$out\"\n else\n out=$(curl -sS -o /dev/null -w \"%{http_code}|%{time_total}\" --max-time \"$TIMEOUT\" \"https://$TARGET\" 2>&1)\n if [[ \"$out\" =~ ^[0-9]+\\|[0-9.]+$ ]]; then\n pass \"HTTPS to $TARGET\" \"$out\"\n else\n fail \"HTTPS to $TARGET\" \"$out\"\n fi\n fi\nfi\n\nsection \"5. PATH / ROUTING\"\ncase \"$(uname -s)\" in\n Darwin)\n # macOS traceroute: -w timeout (sec), -m max hops, -q probes per hop\n info \" traceroute (first 8 hops):\"\n traceroute -n -w 2 -q 1 -m 8 \"$TARGET_IP\" 2>/dev/null | head -10 | sed 's/^/ /' || true\n ;;\n Linux)\n if command -v traceroute >/dev/null 2>&1; then\n info \" traceroute (first 8 hops):\"\n traceroute -n -w 2 -q 1 -m 8 \"$TARGET_IP\" 2>/dev/null | head -10 | sed 's/^/ /' || true\n elif command -v mtr >/dev/null 2>&1; then\n info \" mtr report (5 cycles):\"\n mtr -nrc 5 \"$TARGET_IP\" 2>/dev/null | tail -10 | sed 's/^/ /' || true\n fi\n ;;\nesac\n\nemit_summary\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3748,"content_sha256":"e1a4f272a3a0ff5c037b67f711a46e8f0c8002f0e6ae4feed82c13d05636c243"},{"filename":"scripts/ssh-bootstrap.sh","content":"#!/usr/bin/env bash\n# net-ops :: ssh-bootstrap.sh\n# Establish an SSH session to any target (Windows / macOS / Linux) using\n# password auth via sshpass. Reads password from stdin so it never appears\n# in argv / shell history. Auto-detects target OS and emits the right\n# invocation pattern for follow-up commands.\n#\n# Usage:\n# echo 'password' | scripts/ssh-bootstrap.sh user@host\n# scripts/ssh-bootstrap.sh user@host # interactive prompt\n\nset -euo pipefail\n\nTARGET=\"${1:-}\"\nif [[ -z \"$TARGET\" ]]; then\n echo \"Usage: $0 user@host\" >&2\n exit 1\nfi\n\nif ! command -v sshpass >/dev/null 2>&1; then\n echo \"sshpass not found. Install:\" >&2\n echo \" macOS: brew install hudochenkov/sshpass/sshpass\" >&2\n echo \" Linux: apt install sshpass / dnf install sshpass\" >&2\n exit 1\nfi\n\n# Read password — from stdin if piped, else prompt\nif [[ -t 0 ]]; then\n read -rsp \"Password for $TARGET: \" PASSWORD\n echo\nelse\n read -r PASSWORD\nfi\nexport SSHPASS=\"$PASSWORD\"\n\n# Quick connectivity check (also accepts host key on first contact).\n# Use a probe that works on all three: `uname -s` on Unix, fails on cmd.exe\n# but succeeds on Windows OpenSSH default shell when it's pwsh/powershell.\necho \"Probing $TARGET ...\"\nPROBE=$(sshpass -e ssh \\\n -o StrictHostKeyChecking=accept-new \\\n -o ConnectTimeout=10 \\\n \"$TARGET\" \\\n 'uname -s 2>/dev/null || cmd /c ver 2>nul || ver' 2>&1 | tr -d '\\r')\n\necho \" Response: $(echo \"$PROBE\" | head -3 | tr '\\n' ' | ')\"\n\n# Detect OS family from probe output\nOS=\"\"\ncase \"$PROBE\" in\n *Darwin*) OS=\"macos\" ;;\n *Linux*) OS=\"linux\" ;;\n *Microsoft*|*Windows*) OS=\"windows\" ;;\nesac\n\nif [[ -z \"$OS\" ]]; then\n echo\n echo \"Could not auto-detect OS. Treating as unknown — defaulting to bash transport.\"\n OS=\"unknown\"\nfi\n\necho \"Detected OS family: $OS\"\n\n# Per-OS smoke test\ncase \"$OS\" in\n windows)\n echo\n echo \"Testing PowerShell -EncodedCommand transport ...\"\n TEST_PS='Write-Output (\"PS ready :: \" + $PSVersionTable.PSVersion.ToString())'\n B64=$(printf '%s' \"$TEST_PS\" | iconv -t UTF-16LE | base64)\n sshpass -e ssh \"$TARGET\" \"powershell -NoProfile -EncodedCommand $B64\" 2>&1 | tail -3\n ;;\n macos|linux|unknown)\n echo\n echo \"Testing bash transport ...\"\n sshpass -e ssh \"$TARGET\" 'bash -c \"echo BASH_OK :: \\$(bash --version | head -1)\"' 2>&1 | tail -2\n ;;\nesac\n\n# Per-OS invocation hints\necho\necho \"---\"\ncase \"$OS\" in\n windows)\n cat \u003c\u003cEOF\nReady (Windows target). Run a PowerShell script via:\n\n PS_SCRIPT=\\$(cat skills/net-ops/scripts/windows/probe.ps1)\n B64=\\$(printf '%s' \"\\$PS_SCRIPT\" | iconv -t UTF-16LE | base64)\n SSHPASS='\u003cpassword>' sshpass -e ssh $TARGET \"powershell -NoProfile -EncodedCommand \\$B64\"\n\nDrilldown scripts: nrpt-audit.ps1, nrpt-clean.ps1\n\nFor zero-friction follow-up, install your pubkey on the target:\n Windows admin path: %ProgramData%\\\\ssh\\\\administrators_authorized_keys\n Windows user path: %USERPROFILE%\\\\.ssh\\\\authorized_keys\nEOF\n ;;\n macos)\n cat \u003c\u003cEOF\nReady (macOS target). Run a bash script via:\n\n SSHPASS='\u003cpassword>' sshpass -e ssh $TARGET 'bash -s' \u003c skills/net-ops/scripts/macos/probe.sh\n\nDrilldown scripts: macos/dns-audit.sh, macos/resolver-clean.sh\n\nPersistent access: ssh-copy-id $TARGET\nEOF\n ;;\n linux)\n cat \u003c\u003cEOF\nReady (Linux target). Run a bash script via:\n\n SSHPASS='\u003cpassword>' sshpass -e ssh $TARGET 'bash -s' \u003c skills/net-ops/scripts/linux/probe.sh\n\nDrilldown scripts: linux/dns-audit.sh, linux/resolved-reset.sh\n\nPersistent access: ssh-copy-id $TARGET\nEOF\n ;;\n *)\n echo \"Generic SSH ready. Run commands directly.\"\n ;;\nesac\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3702,"content_sha256":"2fa58d8146cc39ea2cf52b3a1b4689d03982e3a88fe65fceb3454c271d742311"},{"filename":"tests/run.sh","content":"#!/usr/bin/env bash\n# net-ops :: tests/run.sh\n# Lightweight self-tests. Run from the repo root:\n# bash skills/net-ops/tests/run.sh\n#\n# These verify structural and output invariants of the probe scripts WITHOUT\n# trying to simulate broken network state. They catch regressions in:\n# - bash syntax / unbound vars / set -u trips\n# - section labels and ordering\n# - --redact actually masking private addrs / tailnet names\n# - --json producing parseable NDJSON\n# - summary block format\n# - dispatcher routing to the right per-OS script\n\nset -u\n\nPASS=0\nFAIL=0\nFAILED_TESTS=()\n\nassert() {\n local name=\"$1\"; shift\n if \"$@\"; then\n PASS=$((PASS+1))\n printf \" [PASS] %s\\n\" \"$name\"\n else\n FAIL=$((FAIL+1))\n FAILED_TESTS+=(\"$name\")\n printf \" [FAIL] %s\\n\" \"$name\"\n fi\n}\n\ncontains() { local hay=\"$1\" needle=\"$2\"; [[ \"$hay\" == *\"$needle\"* ]]; }\nnot_contains() { local hay=\"$1\" needle=\"$2\"; [[ \"$hay\" != *\"$needle\"* ]]; }\n\n# Locate skill root regardless of invocation dir\nhere=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nroot=\"$(cd \"$here/..\" && pwd)\"\n\necho \"=== net-ops self-tests ===\"\necho \"Root: $root\"\n\n# Determine the local OS probe for testing\ncase \"$(uname -s)\" in\n Darwin) probe=\"$root/scripts/macos/probe.sh\"; audit=\"$root/scripts/macos/dns-audit.sh\" ;;\n Linux) probe=\"$root/scripts/linux/probe.sh\"; audit=\"$root/scripts/linux/dns-audit.sh\" ;;\n *) echo \"Skipping: unsupported OS for local probe tests.\" ; exit 0 ;;\nesac\n\n# ---------------------------------------------------------------------------\necho\necho \"--- Probe structural tests ---\"\n# ---------------------------------------------------------------------------\n\nout=$(bash \"$probe\" 2>&1)\n\nassert \"probe runs without bash error\" \\\n not_contains \"$out\" \"syntax error\"\nassert \"probe runs without unbound variable error\" \\\n not_contains \"$out\" \"unbound variable\"\nassert \"probe emits summary block\" \\\n contains \"$out\" \"=== SUMMARY ===\"\nassert \"probe emits PASS/FAIL counts\" \\\n contains \"$out\" \"PASS:\"\ncheck_all_sections() {\n local out=\"$1\"\n for s in \"1. LINK LAYER\" \"2. IP / ICMP\" \"3. TCP/UDP SOCKET\" \"4. DNS INFRASTRUCTURE\" \"6. APPLICATION\" \"7. KNOWN VPN\"; do\n contains \"$out\" \"=== $s\" || return 1\n done\n # Section 5 has OS-specific naming; match on the common anchor.\n contains \"$out\" \"(the hook layer)\" || return 1\n return 0\n}\nassert \"probe contains all 7 sections\" check_all_sections \"$out\"\n\n# ---------------------------------------------------------------------------\necho\necho \"--- --redact tests ---\"\n# ---------------------------------------------------------------------------\n\nredacted=$(bash \"$probe\" --redact 2>&1)\n\n# Common private patterns that should NEVER appear in redacted output.\n# (We use specific octets that are unlikely to appear in unrelated contexts.)\nassert \"--redact masks 192.168.x.x\" \\\n bash -c '! grep -E \"\\b192\\.168\\.[0-9]+\\.[0-9]+\\b\" \u003c\u003c\u003c \"$0\" | grep -v \"192.168.X.X\" >/dev/null' \"$redacted\"\nassert \"--redact masks .ts.net tailnet names\" \\\n bash -c '! grep -E \"\\b[a-z0-9-]+\\.ts\\.net\\b\" \u003c\u003c\u003c \"$0\" | grep -v \"REDACTED.ts.net\" >/dev/null' \"$redacted\"\nassert \"--redact preserves 100.100.100.100 anchor\" \\\n bash -c '[[ \"$0\" != *\"100.X.X.X\"* ]] || grep -q \"100.100.100.100\" \u003c\u003c\u003c \"$0\"' \"$redacted\"\nassert \"--redact preserves 1.1.1.1 public anchor\" \\\n contains \"$redacted\" \"1.1.1.1\"\n\n# ---------------------------------------------------------------------------\necho\necho \"--- --json tests ---\"\n# ---------------------------------------------------------------------------\n\njson_out=$(bash \"$probe\" --json 2>&1)\n\nassert \"--json emits at least one section record\" \\\n contains \"$json_out\" '\"type\":\"section\"'\nassert \"--json emits at least one check record\" \\\n contains \"$json_out\" '\"type\":\"check\"'\nassert \"--json emits a summary record\" \\\n contains \"$json_out\" '\"type\":\"summary\"'\nassert \"--json summary contains pass count\" \\\n bash -c 'grep -q \"\\\"type\\\":\\\"summary\\\".*\\\"pass\\\":[0-9]\" \u003c\u003c\u003c \"$0\"' \"$json_out\"\n\n# ---------------------------------------------------------------------------\necho\necho \"--- Dispatcher test ---\"\n# ---------------------------------------------------------------------------\n\ndisp_out=$(\"$root/scripts/probe\" 2>&1 | tail -5)\nassert \"dispatcher routes to per-OS probe (summary present)\" \\\n contains \"$disp_out\" \"PASS:\"\n\n# ---------------------------------------------------------------------------\necho\necho \"--- dns-audit smoke test ---\"\n# ---------------------------------------------------------------------------\n\naudit_out=$(bash \"$audit\" 2>&1)\nassert \"dns-audit runs without error\" \\\n not_contains \"$audit_out\" \"syntax error\"\nassert \"dns-audit emits attribution hints section\" \\\n contains \"$audit_out\" \"ATTRIBUTION HINTS\"\n\n# ---------------------------------------------------------------------------\necho\necho \"--- Edge cases ---\"\n# ---------------------------------------------------------------------------\n\n# --json should emit ONLY JSON (no chatter leaking through)\njson_pure=$(bash \"$probe\" --json 2>&1)\nnon_json=$(echo \"$json_pure\" | grep -vc '^{')\nassert \"--json produces pure NDJSON (no non-JSON chatter)\" \\\n bash -c '[[ \"$0\" -eq 0 ]]' \"$non_json\"\n\n# --json + --redact: redacted private addrs AND only JSON\ncombo=$(bash \"$probe\" --json --redact 2>&1)\ncombo_non_json=$(echo \"$combo\" | grep -vc '^{')\ncombo_leaks=$(echo \"$combo\" | grep -E \"\\b192\\.168\\.[0-9]+\\.[0-9]+\\b\" | grep -v \"192.168.X.X\")\nassert \"--json + --redact produces pure NDJSON\" \\\n bash -c '[[ \"$0\" -eq 0 ]]' \"$combo_non_json\"\nassert \"--json + --redact has no private-IP leaks\" \\\n bash -c '[[ -z \"$0\" ]]' \"$combo_leaks\"\n\n# Unknown flag should not crash\nassert \"unknown --frobnicate flag does not crash\" \\\n bash -c 'bash \"$0\" --frobnicate 2>&1 | grep -q \"PASS\\\\|FAIL\"' \"$probe\"\n\n# Help flag prints usage and exits cleanly\nhelp_out=$(bash \"$probe\" --help 2>&1)\nassert \"--help mentions --redact\" \\\n contains \"$help_out\" \"--redact\"\nassert \"--help mentions --json\" \\\n contains \"$help_out\" \"--json\"\nassert \"--help mentions --quick\" \\\n contains \"$help_out\" \"--quick\"\n\n# Dispatcher works from a different cwd\ndisp_remote=$(cd /tmp && \"$root/scripts/probe\" 2>&1 | tail -5)\nassert \"dispatcher works from /tmp (cwd-independent)\" \\\n contains \"$disp_remote\" \"PASS:\"\n\n# ---------------------------------------------------------------------------\necho\necho \"=== TOTAL: $PASS pass, $FAIL fail ===\"\nif [[ \"$FAIL\" -gt 0 ]]; then\n echo \"Failed tests:\"\n for t in \"${FAILED_TESTS[@]}\"; do echo \" - $t\"; done\n exit 1\nfi\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6529,"content_sha256":"6fc66d66a19689b85d33aeff814c28e8fa1f62bf2a371376064b003ba4863988"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Network Operations","type":"text"}]},{"type":"paragraph","content":[{"text":"Diagnose network problems on Windows, macOS, or Linux with a layered ladder that isolates faults to the smallest possible scope, then pattern-match against OS-specific culprits. Designed for the common case: someone reports \"internet broken\" on a box you can shell into (locally or via SSH).","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"The Universal Insight","type":"text"}]},{"type":"paragraph","content":[{"text":"Bypass-tool succeeds while OS-resolver fails is a smoking gun on every platform.","type":"text","marks":[{"type":"strong"}]},{"text":" It means DNS infrastructure is healthy but the operating system's name-resolution path is hooked or misconfigured. The bypass tool differs per OS but the discriminator is identical:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OS","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bypass tool","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OS resolver tool","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"If bypass works but resolver fails","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"nslookup","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Resolve-DnsName","type":"text","marks":[{"type":"code_inline"}]},{"text":", browsers","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NRPT, WFP, HOSTS, LSP, local 127.0.0.1:53 proxy","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"macOS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dig @1.1.1.1","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dscacheutil -q host","type":"text","marks":[{"type":"code_inline"}]},{"text":", browsers","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/etc/resolver/*","type":"text","marks":[{"type":"code_inline"}]},{"text":", scutil DNS, profiles, mDNSResponder, kext","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Linux","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dig @1.1.1.1","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"getent hosts","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"resolvectl query","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"systemd-resolved, ","type":"text"},{"text":"/etc/resolv.conf","type":"text","marks":[{"type":"code_inline"}]},{"text":", NetworkManager, dnsmasq, NSS","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"The bypass tool implements its own resolver and talks straight to UDP/53. The OS resolver tool goes through the full system name-service path including all hooks. Comparing the two narrows the suspect list dramatically.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"The Diagnostic Ladder","type":"text"}]},{"type":"paragraph","content":[{"text":"Walk down the layers in order. ","type":"text"},{"text":"Do not skip rungs.","type":"text","marks":[{"type":"strong"}]},{"text":" Each rung has a binary outcome that eliminates everything above it. Per-OS tools are in ","type":"text"},{"text":"references/diagnostic-ladder.md","type":"text","marks":[{"type":"code_inline"}]},{"text":"; the structure is universal.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"1. Link layer — interface up, valid IP, gateway present\n2. IP reachability — ping public IPs over ICMP\n3. Socket reach. — TCP/443 + UDP/53 to known destinations (raw socket DNS)\n4. DNS infrastructure — bypass tool: nslookup / dig @\u003cserver>\n5. OS resolver path — the hook layer (most interesting on modern systems)\n6. Application — real HTTP request to a real hostname","type":"text"}]},{"type":"paragraph","content":[{"text":"The most common mistake: jumping to rung 6 (\"HTTPS doesn't work, must be a cert / proxy\") when rung 5 is the actual problem (an orphan VPN DNS rule on Windows, a stale ","type":"text"},{"text":"/etc/resolver/","type":"text","marks":[{"type":"code_inline"}]},{"text":" file on macOS, a misconfigured systemd-resolved on Linux). Discipline prevents this.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Identify the target OS","type":"text"}]},{"type":"paragraph","content":[{"text":"If local: ","type":"text"},{"text":"uname -s","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Unix) or check shell environment. If remote over SSH, the bootstrap script auto-detects:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"scripts/ssh-bootstrap.sh \u003cuser>@\u003chost>","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Run the OS-appropriate probe","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OS","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Script","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/windows/probe.ps1","type":"text","marks":[{"type":"code_inline"}]},{"text":" (via ","type":"text"},{"text":"-EncodedCommand","type":"text","marks":[{"type":"code_inline"}]},{"text":" over SSH)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"macOS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/macos/probe.sh","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Linux","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/linux/probe.sh","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Each prints structured ","type":"text"},{"text":"[PASS]/[FAIL]","type":"text","marks":[{"type":"code_inline"}]},{"text":" per rung. Scan for the first FAIL — that's where to drill in.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Drill into the failing layer","type":"text"}]},{"type":"paragraph","content":[{"text":"The interesting failures are almost always rung 5. Per-OS deep-dive scripts:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OS","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Script","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"What it does","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/windows/nrpt-audit.ps1","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dump NRPT rules with attribution + registry forensics","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"macOS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/macos/dns-audit.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dump scutil --dns, /etc/resolver/*, mDNSResponder state, profiles","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Linux","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/linux/dns-audit.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dump systemd-resolved status, resolv.conf chain, NM config, NSS order","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Apply the minimum reversible fix","type":"text"}]},{"type":"paragraph","content":[{"text":"Repair scripts default to ","type":"text"},{"text":"dry-run","type":"text","marks":[{"type":"strong"}]},{"text":" and protect known-good config (Tailscale MagicDNS, MDM-managed entries). Apply only when the dry-run output matches expectation.","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OS","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Repair script","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/windows/nrpt-clean.ps1","type":"text","marks":[{"type":"code_inline"}]},{"text":" (removes orphan NRPT catch-alls, protects Tailscale)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"macOS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/macos/resolver-clean.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" (removes orphan ","type":"text"},{"text":"/etc/resolver/*","type":"text","marks":[{"type":"code_inline"}]},{"text":" from disconnected VPNs)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Linux","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/linux/resolved-reset.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" (resets systemd-resolved per-link config)","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Reference: Smoking Guns","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Platform","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Symptom","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Most likely cause","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Quick test","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"nslookup","type":"text","marks":[{"type":"code_inline"}]},{"text":" works, browsers fail","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Orphan NRPT catch-all (VPN residue)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Get-DnsClientNrptRule | Where Namespace -eq '.'","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Public DoH resolver IPs blocked on 443, other 443 works","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"AV \"Encrypted DNS Detection\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Get-CimInstance -Ns root/SecurityCenter2 -Class AntiVirusProduct","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"macOS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dig","type":"text","marks":[{"type":"code_inline"}]},{"text":" works, browsers fail","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stale ","type":"text"},{"text":"/etc/resolver/*","type":"text","marks":[{"type":"code_inline"}]},{"text":" from disconnected VPN","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ls /etc/resolver/ && scutil --dns | head -40","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"macOS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"All DNS fails post-VPN install","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configuration profile with DNS override","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"profiles list -type configuration","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Linux","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dig","type":"text","marks":[{"type":"code_inline"}]},{"text":" works, ","type":"text"},{"text":"getent hosts","type":"text","marks":[{"type":"code_inline"}]},{"text":" fails","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"systemd-resolved misconfigured","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"resolvectl status","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Linux","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"DNS works on some apps, not others","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NSS order in ","type":"text"},{"text":"/etc/nsswitch.conf","type":"text","marks":[{"type":"code_inline"}]},{"text":" excludes ","type":"text"},{"text":"resolve","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"grep ^hosts /etc/nsswitch.conf","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"All","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"DNS suddenly broken after sleep/wake","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"VPN client failed disconnect cleanup","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OS-specific (see above)","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"SSH Transport Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Windows targets","type":"text"}]},{"type":"paragraph","content":[{"text":"PowerShell-over-SSH has notorious escaping issues. Always pass scripts via ","type":"text"},{"text":"-EncodedCommand","type":"text","marks":[{"type":"code_inline"}]},{"text":" with UTF-16LE base64:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"B64=$(printf '%s' \"$PS_SCRIPT\" | iconv -t UTF-16LE | base64)\nssh \u003ctarget> \"powershell -NoProfile -EncodedCommand $B64\"","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Unix targets (macOS, Linux)","type":"text"}]},{"type":"paragraph","content":[{"text":"Heredoc works cleanly; no special encoding needed:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"ssh \u003ctarget> 'bash -s' \u003c scripts/linux/probe.sh\n# or, with arguments:\nssh \u003ctarget> \"bash -s -- arg1 arg2\" \u003c scripts/linux/probe.sh","type":"text"}]},{"type":"paragraph","content":[{"text":"For consistency, ","type":"text"},{"text":"scripts/ssh-bootstrap.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" handles both transports based on detected OS.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Pattern Recognition","type":"text"}]},{"type":"paragraph","content":[{"text":"After a few sessions, certain symptom triplets become instantly diagnosable. See ","type":"text"},{"text":"references/case-studies.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for worked examples. Hall-of-fame entries:","type":"text"}]},{"type":"paragraph","content":[{"text":"Windows:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"nslookup","type":"text","marks":[{"type":"code_inline"}]},{"text":" works, ","type":"text"},{"text":"Resolve-DnsName","type":"text","marks":[{"type":"code_inline"}]},{"text":" times out identically across all servers, ","type":"text"},{"text":"Invoke-WebRequest","type":"text","marks":[{"type":"code_inline"}]},{"text":" says \"remote name could not be resolved\" → orphan NRPT catch-all from a disconnected VPN. Common gateway IP patterns are listed in ","type":"text"},{"text":"references/common-culprits.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"macOS:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"dig \u003chost>","type":"text","marks":[{"type":"code_inline"}]},{"text":" works, browsers say \"cannot find server,\" ","type":"text"},{"text":"scutil --dns","type":"text","marks":[{"type":"code_inline"}]},{"text":" shows extra \"resolver #N\" entries pointing at private-range gateways with ","type":"text"},{"text":"domain :","type":"text","marks":[{"type":"code_inline"}]},{"text":" listed → leftover ","type":"text"},{"text":"/etc/resolver/\u003cdomain>","type":"text","marks":[{"type":"code_inline"}]},{"text":" files from a disconnected VPN.","type":"text"}]},{"type":"paragraph","content":[{"text":"Linux:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"dig @\u003cpublic-resolver> \u003chost>","type":"text","marks":[{"type":"code_inline"}]},{"text":" works, ","type":"text"},{"text":"getent hosts \u003chost>","type":"text","marks":[{"type":"code_inline"}]},{"text":" fails → ","type":"text"},{"text":"/etc/nsswitch.conf","type":"text","marks":[{"type":"code_inline"}]},{"text":" may have an NSS chain that skips ","type":"text"},{"text":"resolve","type":"text","marks":[{"type":"code_inline"}]},{"text":", OR ","type":"text"},{"text":"/etc/resolv.conf","type":"text","marks":[{"type":"code_inline"}]},{"text":" is no longer symlinked to the systemd-resolved stub.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Safety Notes","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read before write.","type":"text","marks":[{"type":"strong"}]},{"text":" Always dump current state before modifying a resolver config. The forensics may be load-bearing for explaining what happened.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Don't disable security tools without consent.","type":"text","marks":[{"type":"strong"}]},{"text":" AV / firewall hooks are intrusive but legitimate. Pause is preferred over uninstall.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tailscale's name-resolution config looks like junk but is essential.","type":"text","marks":[{"type":"strong"}]},{"text":" Always filter on protected nameserver patterns (","type":"text"},{"text":"100.100.100.100","type":"text","marks":[{"type":"code_inline"}]},{"text":" on all OSes) before bulk-deleting.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Resolver config persists across reboots.","type":"text","marks":[{"type":"strong"}]},{"text":" Removing a rule is forever (until the VPN re-creates it). Confirm the source/comment before deletion.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"macOS profile DNS overrides may be MDM-managed.","type":"text","marks":[{"type":"strong"}]},{"text":" Removing them may violate enterprise policy and may be re-applied automatically. Coordinate with IT.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"References","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/diagnostic-ladder.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — full ladder methodology with per-OS commands per rung","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/common-culprits.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — detection + fix catalog for Windows / macOS / Linux","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/case-studies.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — worked examples and template for adding new ones","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Scripts","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/ssh-bootstrap.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — establish SSH session, auto-detect target OS, emit usable invocation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/windows/probe.ps1","type":"text","marks":[{"type":"code_inline"}]},{"text":" — full layered diagnostic for Windows","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/windows/nrpt-audit.ps1","type":"text","marks":[{"type":"code_inline"}]},{"text":" — NRPT forensics with attribution","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/windows/nrpt-clean.ps1","type":"text","marks":[{"type":"code_inline"}]},{"text":" — safe NRPT cleanup (protects Tailscale)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/macos/probe.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — full layered diagnostic for macOS","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/macos/dns-audit.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — scutil + /etc/resolver + profile + mDNSResponder dump","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/macos/resolver-clean.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — remove orphan /etc/resolver/* files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/linux/probe.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — full layered diagnostic for Linux","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/linux/dns-audit.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — systemd-resolved + NM + NSS + resolv.conf dump","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/linux/resolved-reset.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — reset systemd-resolved per-link state","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"net-ops","author":"@skillopedia","source":{"stars":21,"repo_name":"claude-mods","origin_url":"https://github.com/0xdarkmatter/claude-mods/blob/HEAD/skills/net-ops/SKILL.md","repo_owner":"0xdarkmatter","body_sha256":"6ddaa584bc4a60d8ee1814e7d4ee5abfec2552bad6435bad46344d71b47f32dd","cluster_key":"d3b530c252f2fbbe9a8afcd1644c1d74338944848015ba6c71c7f37d2811106b","clean_bundle":{"format":"clean-skill-bundle-v1","source":"0xdarkmatter/claude-mods/skills/net-ops/SKILL.md","attachments":[{"id":"c5b13357-0ad2-57b8-afd9-5da022798852","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c5b13357-0ad2-57b8-afd9-5da022798852/attachment","path":"assets/.gitkeep","size":0,"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","contentType":"text/plain; charset=utf-8"},{"id":"c8b60501-0864-5023-922c-80e5f3b0b132","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c8b60501-0864-5023-922c-80e5f3b0b132/attachment.md","path":"references/case-studies.md","size":6319,"sha256":"591ca06b603d751cee116bde2b065dab3c9e42e61dfd65e09d93dfe811d18002","contentType":"text/markdown; charset=utf-8"},{"id":"fb478bfa-c381-582e-a576-57a0ae7fac56","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fb478bfa-c381-582e-a576-57a0ae7fac56/attachment.md","path":"references/common-culprits.md","size":14306,"sha256":"293542263c338e7ff49596067814ac52fe8f04e0ab9c01068a5053a5b1aa710d","contentType":"text/markdown; charset=utf-8"},{"id":"59e57ee4-4f56-5290-a338-54cb62a8ef2d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/59e57ee4-4f56-5290-a338-54cb62a8ef2d/attachment.md","path":"references/diagnostic-ladder.md","size":7874,"sha256":"61189db081dacbc9c5e3bec159c5350f28a7b92dd79a6b9e6de7141740738b11","contentType":"text/markdown; charset=utf-8"},{"id":"c80b6242-7540-531e-86c7-3e66bc79a4e6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c80b6242-7540-531e-86c7-3e66bc79a4e6/attachment.sh","path":"scripts/_lib/cache.sh","size":1783,"sha256":"80e58eb52dce92a49e4397d67e42f989daa5bec91588f818af703b886bcdc9ca","contentType":"application/x-sh; charset=utf-8"},{"id":"a018956e-b4bd-5c0f-b397-10d49b5dac1e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a018956e-b4bd-5c0f-b397-10d49b5dac1e/attachment.sh","path":"scripts/_lib/output.sh","size":2728,"sha256":"6d91f8d34bb2c8c54be6bd23b07f9b24c2855d568dddd4afcf35f7d33b6450ff","contentType":"application/x-sh; charset=utf-8"},{"id":"c2115a71-a884-5542-a45c-459d3e9407d5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c2115a71-a884-5542-a45c-459d3e9407d5/attachment.sh","path":"scripts/_lib/redact.sh","size":2707,"sha256":"c197a0a89f927ac7e7deb3a6ffa86d413112592a012eed6c691d518a5e44f1f2","contentType":"application/x-sh; charset=utf-8"},{"id":"45444048-760a-5261-a147-a27bb28fc22c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/45444048-760a-5261-a147-a27bb28fc22c/attachment.sh","path":"scripts/linux/dns-audit.sh","size":3459,"sha256":"15b20bc84bb12310f77b248c19af4c03a6204d291bca8552f74ef33831ba5067","contentType":"application/x-sh; charset=utf-8"},{"id":"d051a3dc-eb4e-58bd-91b5-0d56a01458f1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d051a3dc-eb4e-58bd-91b5-0d56a01458f1/attachment.sh","path":"scripts/linux/probe.sh","size":14262,"sha256":"803a8d69d7b60131f5f7c77bb7dd08293e1d2dbe9f4c63b416a9b5eed4372115","contentType":"application/x-sh; charset=utf-8"},{"id":"d8bfd552-8c4f-562a-b178-49b0d3631724","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d8bfd552-8c4f-562a-b178-49b0d3631724/attachment.sh","path":"scripts/linux/resolved-reset.sh","size":2875,"sha256":"c8d4cd4322ae3ca39f43de6e149be41fcee412ebe1bbf178112be892ed3c152f","contentType":"application/x-sh; charset=utf-8"},{"id":"dce5849f-17a3-5e6c-9f82-9046216f3ede","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/dce5849f-17a3-5e6c-9f82-9046216f3ede/attachment.sh","path":"scripts/macos/dns-audit.sh","size":3859,"sha256":"b26b703bc7f884b37f074b4d1b2e76613b5c2bc48597967ffbcfbce1a9d7a4cd","contentType":"application/x-sh; charset=utf-8"},{"id":"3f861c64-d30b-5aa8-b151-b898ec4294b2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3f861c64-d30b-5aa8-b151-b898ec4294b2/attachment.sh","path":"scripts/macos/probe.sh","size":17785,"sha256":"9f2a25348796cf257aed0606abbac6dc15ea160dc1ce766f9e720ee8e7a8bf09","contentType":"application/x-sh; charset=utf-8"},{"id":"dfca27ab-0e9c-516b-9523-7579e93a381d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/dfca27ab-0e9c-516b-9523-7579e93a381d/attachment.sh","path":"scripts/macos/resolver-clean.sh","size":3146,"sha256":"e5e06c720997a26aea1cab43d29c5322a6ae7dde2bda0d02e50e148f89d41b03","contentType":"application/x-sh; charset=utf-8"},{"id":"4e2584c7-62f2-55f4-8a2a-fe2e41c9a9e3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4e2584c7-62f2-55f4-8a2a-fe2e41c9a9e3/attachment","path":"scripts/probe","size":2258,"sha256":"5852fb3066e107b599811e6b81f71a1e200986bebba65375e10e0419efa5ca89","contentType":"text/plain; charset=utf-8"},{"id":"17c5aa5f-2e63-5efd-bf79-bee62bab46a4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/17c5aa5f-2e63-5efd-bf79-bee62bab46a4/attachment.sh","path":"scripts/reverse-probe.sh","size":3748,"sha256":"e1a4f272a3a0ff5c037b67f711a46e8f0c8002f0e6ae4feed82c13d05636c243","contentType":"application/x-sh; charset=utf-8"},{"id":"b670ea83-1a8d-578c-95a7-09884bb5283d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b670ea83-1a8d-578c-95a7-09884bb5283d/attachment.sh","path":"scripts/ssh-bootstrap.sh","size":3702,"sha256":"2fa58d8146cc39ea2cf52b3a1b4689d03982e3a88fe65fceb3454c271d742311","contentType":"application/x-sh; charset=utf-8"},{"id":"ee3aa49e-f65a-5134-9efb-e31946e3d823","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ee3aa49e-f65a-5134-9efb-e31946e3d823/attachment.ps1","path":"scripts/windows/nrpt-audit.ps1","size":3058,"sha256":"839966d456f4ca50e60379acbc5a02b04fc2d16f4c5771e27130445181abb04d","contentType":"text/plain; charset=utf-8"},{"id":"9f8fdf2b-e86d-5dc6-9ea7-11a0cb13fc19","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9f8fdf2b-e86d-5dc6-9ea7-11a0cb13fc19/attachment.ps1","path":"scripts/windows/nrpt-clean.ps1","size":2762,"sha256":"83cf63056916307d05f7c1f5ba316542113048f584450695fbd4fa6cdf9a2704","contentType":"text/plain; charset=utf-8"},{"id":"3c977dc7-3437-57d1-a08a-a788538c994b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3c977dc7-3437-57d1-a08a-a788538c994b/attachment.ps1","path":"scripts/windows/probe.ps1","size":10697,"sha256":"3dfc05ed3a7b25bcd57e4e3b126d2f5e37763062cca58491cf8117362a8ecc14","contentType":"text/plain; charset=utf-8"},{"id":"f073e94b-da74-5f54-99df-64720134a80c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f073e94b-da74-5f54-99df-64720134a80c/attachment.sh","path":"tests/run.sh","size":6529,"sha256":"6fc66d66a19689b85d33aeff814c28e8fa1f62bf2a371376064b003ba4863988","contentType":"application/x-sh; charset=utf-8"}],"bundle_sha256":"a7bc6f456ffe4079e1d3124db62d6bc68e73e5d4ceb11c669b5c2a870ba23c1a","attachment_count":20,"text_attachments":15,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":5,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/net-ops/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"design-ux","category_label":"Design"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"design-ux","metadata":{"author":"claude-mods","related-skills":"debug-ops, network-tools"},"import_tag":"clean-skills-v1","description":"Cross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.","allowed-tools":"Read Write Bash"}},"renderedAt":1782986995766}

Network Operations Diagnose network problems on Windows, macOS, or Linux with a layered ladder that isolates faults to the smallest possible scope, then pattern-match against OS-specific culprits. Designed for the common case: someone reports "internet broken" on a box you can shell into (locally or via SSH). The Universal Insight Bypass-tool succeeds while OS-resolver fails is a smoking gun on every platform. It means DNS infrastructure is healthy but the operating system's name-resolution path is hooked or misconfigured. The bypass tool differs per OS but the discriminator is identical: | O…