Просмотр исходного кода

feat(mac-ops): Add 3 scripts — kext-audit, firewall-audit, network-locations

kext-audit.sh         Loaded kexts (Intel + Apple Silicon kmutil),
                      installed system extensions, kext load failures,
                      pending kext approval, SIP + bputil security policy,
                      known-problematic vendor pattern matches.

firewall-audit.sh     Application Layer Firewall (socketfilterfw),
                      Packet Filter (pf) state + anchors, Network
                      Extension content filters, recent firewall denials,
                      VPN/tunnel inventory.

network-locations.sh  Network Location profiles, per-service DNS/proxy/
                      search-domain config, service priority order,
                      stale/disabled service detection.

Also: apple-silicon-specifics.md reference catalogs M1+ vs Intel diffs
(boot recovery, security policy tiers, kext deprecation, panic format
ARM64 vs x86_64, SMC absorption into SoC).

Plus: health-audit verdict block now does cross-script wayfinding — points
to the right drilldown script when a fail is detected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0xDarkMatter 3 недель назад
Родитель
Сommit
818f00a072

+ 1 - 1
.claude-plugin/plugin.json

@@ -1,6 +1,6 @@
 {
   "name": "claude-mods",
-  "version": "2.7.0",
+  "version": "2.7.1",
   "description": "Custom commands, skills, and agents for Claude Code - session continuity, 23 expert agents, 78 skills, 2 commands, 6 rules, 4 hooks, 13 output styles, modern CLI tools",
   "author": "0xDarkMatter",
   "repository": "https://github.com/0xDarkMatter/claude-mods",

Разница между файлами не показана из-за своего большого размера
+ 3 - 0
README.md


+ 3 - 0
skills/mac-ops/SKILL.md

@@ -83,6 +83,9 @@ Produces a verdict block: hardware events, storage health per volume, recent pan
 | Mac waking at night | `scripts/wake-reasons.sh` — pmset log breakdown by reason class |
 | Spotlight broken / mds CPU spike | `scripts/spotlight-status.sh` — index state per volume, common fixes |
 | Storage "full" but disk usage doesn't add up | `scripts/storage-pressure.sh` — APFS snapshots, local Time Machine, purgeable bytes |
+| Kernel panic blames a kext / loaded kext audit | `scripts/kext-audit.sh` — third-party kexts + system extensions + SIP/security policy state |
+| Firewall behavior / VPN tunnel inventory | `scripts/firewall-audit.sh` — ALF + pf + Network Extension content filters + utun inventory |
+| Network preferences across location profiles | `scripts/network-locations.sh` — DNS / proxy / search domains per location, service order |
 
 ### 3. Apply the minimum reversible fix
 

+ 193 - 0
skills/mac-ops/references/apple-silicon-specifics.md

@@ -0,0 +1,193 @@
+# Apple Silicon Specifics
+
+Load this when working on M1/M2/M3/M4 Macs and the diagnostic behavior differs from Intel. Apple Silicon changed enough fundamental boot, security, and panic surface that some Intel-era assumptions don't carry over.
+
+## Contents
+
+1. [Detecting Apple Silicon](#detecting-apple-silicon)
+2. [Boot recovery](#boot-recovery)
+3. [Security policy](#security-policy)
+4. [Kexts vs system extensions](#kexts-vs-system-extensions)
+5. [Panic format differences](#panic-format-differences)
+6. [SMC / Secure Enclave](#smc--secure-enclave)
+7. [Battery + power](#battery--power)
+8. [What carried over unchanged](#what-carried-over-unchanged)
+
+## Detecting Apple Silicon
+
+```bash
+uname -m              # arm64 → Apple Silicon, x86_64 → Intel
+sysctl -n machdep.cpu.brand_string
+sysctl -n hw.model    # e.g. "Mac15,3"
+```
+
+For scripts that need to branch:
+
+```bash
+if [[ "$(uname -m)" == "arm64" ]]; then
+    # Apple Silicon path
+fi
+```
+
+The mac-ops common.sh provides `is_apple_silicon` as a function.
+
+## Boot recovery
+
+**Intel:**
+- `Cmd-R` at boot → recoveryOS
+- `Cmd-Shift-R` → Internet Recovery (downloads recoveryOS)
+- `Cmd-Option-P-R` → reset NVRAM
+- `Cmd-Option-Shift-Cmd-R` → factory original macOS
+- Shift → Safe Boot
+- `Cmd-S` → single-user mode
+- `Cmd-V` → verbose boot
+- `T` → Target Disk Mode (FireWire/Thunderbolt)
+
+**Apple Silicon (M1+):**
+- Hold power button at boot → "Loading startup options" → choose Options
+- All recovery modes accessed from the startup options screen, not from boot-key combos
+- **No** single-user mode
+- **No** NVRAM reset key combo (NVRAM behavior is different; `sudo nvram -c` from running macOS)
+- Safe Boot: hold power, then hold Shift while choosing volume
+- Verbose Boot: enable via `nvram boot-args="-v"` from running macOS
+- Share Disk (Apple Silicon's Target Disk Mode equivalent): recoveryOS → Utilities → Share Disk
+
+## Security policy
+
+Apple Silicon introduces **per-volume security policy** via the **Local Policy Object**. This controls:
+
+- Whether kernel extensions can load at all
+- Whether unsigned kernel extensions are allowed
+- Whether the OS can be booted in reduced security mode
+
+Inspect (recoveryOS only):
+
+```bash
+bputil -d            # display
+```
+
+Modify (recoveryOS, requires admin auth):
+
+```bash
+bputil -k            # allow kernel extensions
+bputil --set-local-boot-policy-version <version>
+```
+
+Three tiers:
+
+| Tier | Description | What it gates |
+|---|---|---|
+| **Full Security** | Default. Only Apple-signed code, only the version of macOS this Mac was installed with | Maximum security; no kext loading |
+| **Reduced Security** | Allow installer-signed kexts; allow third-party kernel extensions | Required to load any kext on Apple Silicon |
+| **Permissive Security** | Boot any signed code; allow ad-hoc signed | Used by developers; do not run permissively in normal use |
+
+To install a third-party kext on Apple Silicon, you must:
+
+1. Boot to recoveryOS
+2. Run `bputil` to set Reduced Security
+3. Approve "Allow user management of kernel extensions"
+4. Boot normally
+5. Install the kext
+6. Approve in System Settings → Privacy & Security
+7. Reboot
+
+This is by design — kext loading is significantly harder on Apple Silicon than on Intel.
+
+## Kexts vs system extensions
+
+**Intel:**
+- Kexts in `/Library/Extensions/` + `/System/Library/Extensions/`
+- Loaded via `kextload` / `kextd`
+- Inspect with `kextstat`
+
+**Apple Silicon:**
+- Kexts deprecated; most things have moved to **System Extensions** in `/Library/SystemExtensions/<UUID>/`
+- Run in user-mode with privileged-API XPC channels
+- Inspect with `systemextensionsctl list`
+- Loaded via `sysextd`
+- Kexts still possible (Reduced Security mode) but discouraged
+
+For diagnostic scripts:
+
+| Concern | Intel | Apple Silicon |
+|---|---|---|
+| Inventory kexts | `kextstat -l` | `kmutil showloaded` |
+| Inventory system extensions | `systemextensionsctl list` | `systemextensionsctl list` |
+| Kext load failures | `log show ... process == "kextd"` | `log show ... process == "kmutil"` |
+
+The `scripts/kext-audit.sh` script handles both paths.
+
+## Panic format differences
+
+| Concern | Intel | Apple Silicon |
+|---|---|---|
+| Panic file extension | `.panic` (legacy) + `.ips` | `.ips` only on recent macOS |
+| Stack trace addressing | x86_64 | ARM64 |
+| Common panic string | `page_fault`, `general_protection` | `Kernel data abort`, `panic_kthread` |
+| iBoot version line | absent | present (`iBoot-XXXXXXX.XX.X`) |
+| "secure boot?" line | present | present |
+| SMC dump in panic | sometimes | absent (no user-accessible SMC) |
+
+When triaging panics on Apple Silicon:
+
+1. Backtrace addresses are ARM64, so symbolication is different
+2. Many Intel-era kext crashes can't happen (those kexts won't load)
+3. Boot policy version + secure boot state should appear at the bottom of the report
+
+## SMC / Secure Enclave
+
+**Intel:**
+- SMC (System Management Controller) is a separate chip controlling power, thermals, lid sensor, etc.
+- User-resetable: shut down → hold Shift+Control+Option+Power for 10 seconds
+- SMC events appear in unified log
+- Panics can reference SMC state
+
+**Apple Silicon:**
+- SMC functions absorbed into the SoC (T2-equivalent functionality)
+- **Not user-resetable** in the traditional sense
+- Power button hold + recoveryOS handles equivalent "reset" via firmware reload
+- Secure Enclave is the SoC's secure coprocessor (replaces both SMC + T2 Secure Enclave)
+
+For "reset SMC" troubleshooting on Apple Silicon: simply shut down, wait 30s, power on. There's no equivalent reset combo; the SoC handles its own state.
+
+## Battery + power
+
+`pmset -g` behavior is largely identical but:
+
+- Apple Silicon has **standby** that's deeper than Intel's
+- Power Nap is on by default on Apple Silicon laptops (System Settings → Battery → Options)
+- Dark wake budget is more aggressive
+- `kernel_task` CPU on Apple Silicon is **not** a thermal proxy the way it was on Intel — instead, SoC-level throttling happens silently
+
+Battery health:
+
+```bash
+system_profiler SPPowerDataType | grep -E "Cycle Count|Condition|Maximum Capacity"
+```
+
+Apple Silicon laptops support a "Maximum Capacity" metric out of the box. Intel Macs require third-party utilities (coconutBattery) for that data.
+
+## What carried over unchanged
+
+Don't over-correct. These work identically on Apple Silicon:
+
+- `launchd` plist semantics
+- TCC database structure and location
+- APFS commands (`diskutil apfs ...`)
+- `log show` queries
+- `pmset -g log` format (largely)
+- Network settings (`networksetup`, `scutil`)
+- Time Machine (`tmutil`)
+- Spotlight (`mdutil`)
+- SSH server configuration
+- Configuration profiles
+- DiagnosticReports location
+
+The big-shift areas are: kexts, security policy, boot recovery UX, SMC, and panic format. Almost everything else stayed put.
+
+## Cross-references
+
+- `scripts/kext-audit.sh` — handles both Intel kexts and Apple Silicon system extensions
+- `scripts/panic-triage.sh` — handles both `.panic` and `.ips` formats
+- For boot recovery procedures, see `recovery-patterns.md`
+- For panic decoding, see `panic-codes.md`

+ 137 - 0
skills/mac-ops/scripts/firewall-audit.sh

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

+ 25 - 6
skills/mac-ops/scripts/health-audit.sh

@@ -242,11 +242,30 @@ emit_summary
 
 if [[ "$JSON_MODE" -eq 0 ]] && [[ -n "$FIRST_FAIL" ]]; then
     case "$FIRST_FAIL" in
-        *"PANIC"*)     echo "  Next: scripts/panic-triage.sh  # decode the most recent panic" ;;
-        *"STORAGE"*)   echo "  Next: scripts/disk-health.sh -v /  # drill into rung 2" ;;
-        *"STARTUP"*)   echo "  Next: scripts/startup-audit.sh  # inventory and cull bloat" ;;
-        *"TCC"*)       echo "  Next: scripts/tcc-audit.sh  # see which app/service is denied" ;;
-        *"WAKE"*)      echo "  Next: scripts/wake-reasons.sh  # break down wake causes" ;;
-        *) echo "  Next: re-run with --verbose, then check references/" ;;
+        *"PANIC"*|*"panic"*)
+            echo "  Next: scripts/panic-triage.sh  # decode the most recent panic + pre-panic timeline" ;;
+        *"STORAGE"*|*"IO errors"*|*"APFS"*)
+            echo "  Next: scripts/disk-health.sh -v /  # APFS + IO errors + snapshot bloat" ;;
+        *"snapshot"*|*"Free space"*|*"Local Time Machine"*)
+            echo "  Next: scripts/storage-pressure.sh  # explain disk pressure / snapshot bloat" ;;
+        *"STARTUP"*|*"LaunchAgent"*|*"LaunchDaemon"*|*"Login Items"*)
+            echo "  Next: scripts/startup-audit.sh  # full inventory; safe-disable-startup.sh to cull" ;;
+        *"TCC"*|*"denial"*)
+            echo "  Next: scripts/tcc-audit.sh --denied  # see which app/service is being denied" ;;
+        *"Wake"*|*"WAKE"*)
+            echo "  Next: scripts/wake-reasons.sh --since 7d  # classify wakes by cause" ;;
+        *"Thermal"*|*"Battery"*|*"shutdown"*)
+            echo "  Next: open System Settings → Battery → Options; check pmset -g custom" ;;
+        *"helper tool"*)
+            echo "  Next: ls /Library/PrivilegedHelperTools/  # remove orphans manually with sudo rm" ;;
+        *)
+            echo "  Next: re-run with --verbose, then check references/" ;;
     esac
 fi
+
+# Friendly status if nothing failed
+if [[ "$JSON_MODE" -eq 0 ]] && [[ -z "$FIRST_FAIL" ]] && [[ "$WARN_COUNT" -eq 0 ]]; then
+    echo "  ✓ System looks clean across all 8 rungs."
+elif [[ "$JSON_MODE" -eq 0 ]] && [[ -z "$FIRST_FAIL" ]] && [[ "$WARN_COUNT" -gt 0 ]]; then
+    echo "  ✓ No FAILs. $WARN_COUNT WARN entries above worth scanning."
+fi

+ 155 - 0
skills/mac-ops/scripts/kext-audit.sh

@@ -0,0 +1,155 @@
+#!/usr/bin/env bash
+# mac-ops :: kext-audit.sh
+# Inventory loaded kernel extensions + system extensions.
+#
+# Why: kexts and system extensions run with kernel privileges. A misbehaving
+# one can panic the system, leak memory, or hold a system-wide lock. They're
+# the #1 cause of "Mac kernel panic" on machines that get them.
+
+set -u
+
+while [[ $# -gt 0 ]]; do
+    case "$1" in
+        --help|-h)
+            cat <<EOF
+Usage: $0 [options]
+
+  --json, --redact, --quiet, --verbose
+
+Reports:
+  1. Loaded kexts (kextstat) — third-party highlighted
+  2. Installed system extensions (systemextensionsctl list)
+  3. Kext load failures from log
+  4. Pending kext approval (apps that requested kext but were denied)
+  5. SIP and kernel security policy state
+
+On Apple Silicon (M1+), kexts are deprecated in favor of system extensions
+which run in userspace. This script reports both because some legacy products
+still ship kexts even on Apple Silicon (via boot policy reduction).
+EOF
+            exit 0 ;;
+        *) shift ;;
+    esac
+done
+
+source "$(dirname "$0")/_lib/common.sh"
+parse_common_flags "$@"
+maybe_filter_self "$@"
+
+# ----------------------------------------------------------------------------
+section "1. LOADED KEXTS"
+# ----------------------------------------------------------------------------
+total_kexts=$(kextstat -l 2>/dev/null | wc -l | tr -d ' ')
+log_info "Loaded kexts (total)" "$total_kexts"
+
+# Third-party kexts — anything not com.apple.*
+third_party=$(kextstat -l 2>/dev/null | awk '{print $6}' | grep -v "^com.apple\." | grep -v "^$" | sort -u)
+third_party_count=$(echo "$third_party" | grep -c . 2>/dev/null || echo 0)
+
+if [[ "$third_party_count" -gt 0 ]]; then
+    log_warn "Third-party kexts" "$third_party_count — primary panic suspects"
+    note "  Third-party kexts (loaded right now):"
+    echo "$third_party" | sed 's/^/    /'
+else
+    log_pass "Third-party kexts" "0 — clean kernel"
+fi
+
+# ----------------------------------------------------------------------------
+section "2. SYSTEM EXTENSIONS"
+# ----------------------------------------------------------------------------
+if command -v systemextensionsctl >/dev/null 2>&1; then
+    sysext_out=$(systemextensionsctl list 2>/dev/null)
+    if [[ -n "$sysext_out" ]]; then
+        # The output has 0+ extensions per team. Skip the header line.
+        ext_lines=$(echo "$sysext_out" | grep -E "^\s*\*?\s+[a-fA-F0-9]" || true)
+        if [[ -n "$ext_lines" ]]; then
+            ext_count=$(echo "$ext_lines" | wc -l | tr -d ' ')
+            log_info "Installed system extensions" "$ext_count"
+            note "  System extensions (team-id, bundle-id, name, state):"
+            echo "$ext_lines" | head -20 | sed 's/^/    /'
+        else
+            log_pass "Installed system extensions" "0"
+        fi
+    fi
+else
+    log_info "systemextensionsctl" "not available (older macOS?)"
+fi
+
+# ----------------------------------------------------------------------------
+section "3. RECENT KEXT LOAD FAILURES"
+# ----------------------------------------------------------------------------
+load_fails=$(log show --last 7d --style compact \
+    --predicate '(process == "kextd" OR process == "kernel") AND (eventMessage CONTAINS[c] "kext" AND (messageType == "Error" OR messageType == "Fault"))' \
+    2>/dev/null | head -20)
+
+if [[ -n "$load_fails" ]]; then
+    n=$(echo "$load_fails" | wc -l | tr -d ' ')
+    log_warn "Kext load failures (7d)" "$n events"
+    echo "$load_fails" | head -5 | sed 's/^/    /'
+else
+    log_pass "Kext load failures (7d)" "none"
+fi
+
+# ----------------------------------------------------------------------------
+section "4. PENDING KEXT APPROVAL"
+# ----------------------------------------------------------------------------
+# Apps that have requested kext load but were denied — usually because user
+# hasn't approved in System Settings → Privacy & Security
+pending=$(log show --last 30d --style compact \
+    --predicate 'eventMessage CONTAINS[c] "kext approval"' \
+    2>/dev/null | tail -5)
+if [[ -n "$pending" ]]; then
+    log_warn "Pending kext approvals (30d)" "see below"
+    echo "$pending" | sed 's/^/    /'
+else
+    log_pass "Pending kext approvals" "none"
+fi
+
+# ----------------------------------------------------------------------------
+section "5. SECURITY POLICY"
+# ----------------------------------------------------------------------------
+# SIP status
+sip_status=$(csrutil status 2>/dev/null | head -1 | awk -F': *' '{print $2}' | tr -d '.')
+case "$sip_status" in
+    *enabled*) log_pass "SIP" "$sip_status" ;;
+    *disabled*) log_warn "SIP" "$sip_status — kernel security weakened" ;;
+    *) log_info "SIP" "${sip_status:-unknown}" ;;
+esac
+
+# On Apple Silicon: bputil reports boot policy
+if is_apple_silicon && command -v bputil >/dev/null 2>&1; then
+    note "  Apple Silicon boot policy (requires sudo for detail):"
+    sudo -n bputil -d 2>/dev/null | grep -E "Security Policy|Manage Kernel Extensions|Allow User Kernel Extensions" | head -5 | sed 's/^/    /' || \
+        note "    (sudo required for full bputil read)"
+fi
+
+# Apple Silicon specific kext loading state
+if is_apple_silicon; then
+    kext_loading=$(kmutil showloaded 2>/dev/null | wc -l | tr -d ' ')
+    log_info "kmutil showloaded count" "$kext_loading"
+fi
+
+# ----------------------------------------------------------------------------
+section "6. VENDOR PATTERNS"
+# ----------------------------------------------------------------------------
+# Known panic-prone vendors
+note "  Scanning for known-problematic kexts:"
+for pattern in "eltima" "paragon" "eset" "kaspersky" "norton" "sophos" "bitdefender"; do
+    matches=$(kextstat -l 2>/dev/null | awk '{print $6}' | grep -i "$pattern" || true)
+    if [[ -n "$matches" ]]; then
+        log_warn "Vendor kext: $pattern" "$(echo "$matches" | wc -l | tr -d ' ') loaded"
+        echo "$matches" | head -3 | sed 's/^/    /'
+    fi
+done
+
+# ----------------------------------------------------------------------------
+emit_summary
+
+if [[ "$JSON_MODE" -eq 0 ]]; then
+    echo
+    note "  To uninstall a system extension:"
+    note "    systemextensionsctl uninstall <team-id> <bundle-id>"
+    note "  To inspect a specific kext:"
+    note "    kextstat -l | grep <name>"
+    note "    kmutil showloaded | grep <name>   # Apple Silicon"
+fi

+ 110 - 0
skills/mac-ops/scripts/network-locations.sh

@@ -0,0 +1,110 @@
+#!/usr/bin/env bash
+# mac-ops :: network-locations.sh
+# Inventory macOS Network Locations (System Settings → Network → ... → Locations).
+# Each location is a separate set of network preferences — useful for "home vs
+# office vs cafe" profiles. A stale location may have wrong DNS or proxy.
+
+set -u
+
+while [[ $# -gt 0 ]]; do
+    case "$1" in
+        --help|-h)
+            cat <<EOF
+Usage: $0 [options]
+
+  --json, --redact, --quiet, --verbose
+
+Reports:
+  1. Configured network locations + active location
+  2. Per-location DNS / proxy / search-domain config
+  3. Stale locations referencing missing network services
+  4. Network service order (which interface "wins" for the default route)
+
+Switch location:
+  System Settings → Network → ... → Locations → choose
+  Or CLI: networksetup -switchtolocation "Location Name"
+EOF
+            exit 0 ;;
+        *) shift ;;
+    esac
+done
+
+source "$(dirname "$0")/_lib/common.sh"
+parse_common_flags "$@"
+maybe_filter_self "$@"
+
+# ----------------------------------------------------------------------------
+section "1. CONFIGURED LOCATIONS"
+# ----------------------------------------------------------------------------
+current=$(networksetup -getcurrentlocation 2>/dev/null)
+locations=$(networksetup -listlocations 2>/dev/null)
+loc_count=$(echo "$locations" | grep -c . 2>/dev/null || echo 0)
+
+log_info "Network locations configured" "$loc_count"
+note "  Current location: $current"
+note "  All locations:"
+echo "$locations" | sed 's/^/    /'
+
+# ----------------------------------------------------------------------------
+section "2. NETWORK SERVICE ORDER"
+# ----------------------------------------------------------------------------
+note "  Priority order (highest first — first reachable wins default route):"
+networksetup -listnetworkserviceorder 2>/dev/null | head -20 | sed 's/^/    /'
+
+# ----------------------------------------------------------------------------
+section "3. ACTIVE LOCATION DNS / PROXY STATE"
+# ----------------------------------------------------------------------------
+note "  Per-service DNS:"
+networksetup -listallnetworkservices 2>/dev/null | tail -n +2 | while read -r svc; do
+    [[ "$svc" == \** ]] && continue   # disabled
+    dns=$(networksetup -getdnsservers "$svc" 2>/dev/null)
+    [[ "$dns" == *"aren't any"* ]] && dns="(none)"
+    printf "    %-35s %s\n" "$svc:" "$dns"
+done
+
+note ""
+note "  Per-service search domains:"
+networksetup -listallnetworkservices 2>/dev/null | tail -n +2 | while read -r svc; do
+    [[ "$svc" == \** ]] && continue
+    sd=$(networksetup -getsearchdomains "$svc" 2>/dev/null)
+    [[ "$sd" == *"aren't any"* ]] && continue
+    printf "    %-35s %s\n" "$svc:" "$sd"
+done
+
+# Web proxy state
+note ""
+note "  Web proxy state:"
+scutil --proxy 2>/dev/null | grep -E "HTTPEnable|HTTPSEnable|ProxyAutoConfigEnable|ProxyAutoConfigURLString" | sed 's/^/    /'
+
+# ----------------------------------------------------------------------------
+section "4. NETWORK PREFERENCES PLIST INSPECTION"
+# ----------------------------------------------------------------------------
+# /Library/Preferences/SystemConfiguration/preferences.plist holds all locations
+# We can extract the location list defensively
+prefs_plist="/Library/Preferences/SystemConfiguration/preferences.plist"
+if [[ -r "$prefs_plist" ]]; then
+    # Extract just the Sets dict, which has one entry per location
+    set_count=$(plutil -extract Sets raw -o - "$prefs_plist" 2>/dev/null | wc -l | tr -d ' ')
+    log_info "preferences.plist Sets count" "$set_count"
+fi
+
+# ----------------------------------------------------------------------------
+section "5. STALE / DISABLED SERVICES"
+# ----------------------------------------------------------------------------
+# Asterisked services in listallnetworkservices are disabled
+disabled=$(networksetup -listallnetworkservices 2>/dev/null | grep '^\*' || true)
+if [[ -n "$disabled" ]]; then
+    n=$(echo "$disabled" | wc -l | tr -d ' ')
+    log_info "Disabled network services" "$n"
+    echo "$disabled" | sed 's/^/    /'
+else
+    log_pass "Disabled network services" "0"
+fi
+
+# Services referencing missing hardware
+note ""
+note "  Network services check:"
+networksetup -listallhardwareports 2>/dev/null | awk '/Hardware Port|Device/{print}' | head -15 | sed 's/^/    /'
+
+# ----------------------------------------------------------------------------
+emit_summary

+ 2 - 1
skills/mac-ops/tests/run.sh

@@ -173,6 +173,7 @@ expected_scripts=(
     health-audit.sh panic-triage.sh startup-audit.sh safe-disable-startup.sh
     disk-health.sh drive-dependencies.sh boot-perf.sh recover-clone.sh
     tcc-audit.sh wake-reasons.sh spotlight-status.sh storage-pressure.sh
+    kext-audit.sh firewall-audit.sh network-locations.sh
 )
 for s in "${expected_scripts[@]}"; do
     assert "script exists: $s" test -f "$root/scripts/$s"
@@ -186,7 +187,7 @@ echo "--- All 7 reference docs present ---"
 expected_refs=(
     storage-events.md recovery-patterns.md tcc-mechanics.md
     launchd-deep-dive.md panic-codes.md startup-mechanisms.md
-    remote-diagnostics.md
+    remote-diagnostics.md apple-silicon-specifics.md
 )
 for r in "${expected_refs[@]}"; do
     assert "reference exists: $r" test -f "$root/references/$r"

Некоторые файлы не были показаны из-за большого количества измененных файлов