harden macOS networking port
This commit is contained in:
@@ -7,6 +7,7 @@ import random
|
||||
import re
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import winreg
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@@ -14,6 +15,9 @@ from .firewall import is_admin
|
||||
from .mac_spoof import list_nics
|
||||
from .win_compat import probe as _win_probe
|
||||
|
||||
if sys.platform == "darwin":
|
||||
from .macos_privileged import run_shell, run_shell_as_admin, shell_quote
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_WEBRTC_CHROME = r"SOFTWARE\Policies\Google\Chrome"
|
||||
@@ -26,6 +30,43 @@ _WEBRTC_VALUE_STR = "WebRtcIPHandling"
|
||||
_WEBRTC_DISABLE_STR = "disable_non_proxied_udp"
|
||||
|
||||
|
||||
def _mac_network_services() -> list[str]:
|
||||
if sys.platform != "darwin":
|
||||
return []
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["networksetup", "-listallnetworkservices"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
except Exception:
|
||||
return []
|
||||
services: list[str] = []
|
||||
for line in (r.stdout or "").splitlines():
|
||||
name = line.strip()
|
||||
if not name or name.startswith("An asterisk"):
|
||||
continue
|
||||
services.append(name.lstrip("*").strip())
|
||||
return services
|
||||
|
||||
|
||||
def _mac_ipv6_state(service: str) -> str:
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["networksetup", "-getinfo", service],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=8,
|
||||
)
|
||||
for line in (r.stdout or "").splitlines():
|
||||
if line.strip().startswith("IPv6:"):
|
||||
return line.split(":", 1)[1].strip() or "Automatic"
|
||||
except Exception:
|
||||
pass
|
||||
return "Automatic"
|
||||
|
||||
|
||||
@dataclass
|
||||
class FingerprintAudit:
|
||||
lines: list[str] = field(default_factory=list)
|
||||
@@ -51,6 +92,12 @@ def _run_ps(script: str, timeout: float = 15.0) -> str:
|
||||
|
||||
|
||||
def get_computer_name() -> str:
|
||||
if sys.platform == "darwin":
|
||||
try:
|
||||
r = subprocess.run(["scutil", "--get", "ComputerName"], capture_output=True, text=True, timeout=5)
|
||||
return (r.stdout or "").strip() or platform.node() or ""
|
||||
except Exception:
|
||||
return platform.node() or ""
|
||||
try:
|
||||
import os
|
||||
return os.environ.get("COMPUTERNAME", "") or platform.node() or ""
|
||||
@@ -77,6 +124,19 @@ def random_hostname(prefix: str = "PC") -> str:
|
||||
|
||||
def set_computer_name(name: str) -> tuple[bool, str]:
|
||||
"""Set NetBIOS / computer name (Admin). Reboot may be required for all apps."""
|
||||
if sys.platform == "darwin":
|
||||
clean = re.sub(r"[^A-Za-z0-9\-]", "", name)[:63]
|
||||
local = clean[:63].strip("-") or random_hostname("MAC")
|
||||
script = "\n".join([
|
||||
f"scutil --set ComputerName {shell_quote(clean)}",
|
||||
f"scutil --set HostName {shell_quote(clean)}",
|
||||
f"scutil --set LocalHostName {shell_quote(local)}",
|
||||
"dscacheutil -flushcache || true",
|
||||
])
|
||||
code, _, err = run_shell_as_admin(script)
|
||||
if code == 0:
|
||||
return True, f"macOS host identity set to {clean}."
|
||||
return False, f"macOS hostname change failed: {err.strip() or 'permission denied'}"
|
||||
if not is_admin():
|
||||
return False, "Administrator required to change computer name."
|
||||
name = re.sub(r"[^A-Za-z0-9\-]", "", name)[:15]
|
||||
@@ -110,6 +170,24 @@ def set_computer_name(name: str) -> tuple[bool, str]:
|
||||
|
||||
def disable_ipv6_on_adapters() -> tuple[list[str], list[str]]:
|
||||
"""Disable IPv6 binding on up physical adapters. Returns (adapter names, log lines)."""
|
||||
if sys.platform == "darwin":
|
||||
services = _mac_network_services()
|
||||
if not services:
|
||||
return [], ["IPv6 disable skipped: no macOS network services found."]
|
||||
states: list[str] = []
|
||||
commands = ["set -e"]
|
||||
for service in services:
|
||||
state = _mac_ipv6_state(service)
|
||||
if state.lower() == "off":
|
||||
continue
|
||||
states.append(f"{service}|{state}")
|
||||
commands.append(f"networksetup -setv6off {shell_quote(service)}")
|
||||
if not states:
|
||||
return [], ["IPv6 already off on all detected macOS network services."]
|
||||
code, _, err = run_shell_as_admin("\n".join(commands))
|
||||
if code != 0:
|
||||
return [], [f"IPv6 disable failed: {err.strip() or 'permission denied'}"]
|
||||
return states, [f"IPv6 disabled on {len(states)} macOS network service(s)."]
|
||||
if not is_admin():
|
||||
return [], ["IPv6 disable skipped (not Admin)."]
|
||||
if not _win_probe().has_net_cmdlets:
|
||||
@@ -144,6 +222,23 @@ def disable_ipv6_on_adapters() -> tuple[list[str], list[str]]:
|
||||
|
||||
|
||||
def enable_ipv6_on_adapters(adapters: list[str]) -> list[str]:
|
||||
if sys.platform == "darwin":
|
||||
if not adapters:
|
||||
return []
|
||||
commands = ["set -e"]
|
||||
for item in adapters:
|
||||
service, _, state = item.partition("|")
|
||||
state_l = (state or "Automatic").lower()
|
||||
if "link-local" in state_l:
|
||||
commands.append(f"networksetup -setv6linklocal {shell_quote(service)}")
|
||||
elif "manual" in state_l:
|
||||
commands.append(f"networksetup -setv6automatic {shell_quote(service)}")
|
||||
else:
|
||||
commands.append(f"networksetup -setv6automatic {shell_quote(service)}")
|
||||
code, _, err = run_shell_as_admin("\n".join(commands))
|
||||
if code != 0:
|
||||
return [f"IPv6 restore failed: {err.strip() or 'permission denied'}"]
|
||||
return [f"IPv6 restored on {len(adapters)} macOS network service(s)."]
|
||||
if not is_admin() or not adapters:
|
||||
return []
|
||||
if not _win_probe().has_net_cmdlets:
|
||||
@@ -171,6 +266,8 @@ def apply_webrtc_hardening(enable: bool) -> tuple[bool, str]:
|
||||
the modern REG_SZ key (WebRtcIPHandling=disable_non_proxied_udp) so that
|
||||
all Chrome/Edge versions are covered.
|
||||
"""
|
||||
if sys.platform == "darwin":
|
||||
return False, "Chrome/Edge WebRTC policy is not applied on macOS; use the hardened Firefox profile controls."
|
||||
if not is_admin():
|
||||
return False, "Administrator required for browser WebRTC policy."
|
||||
paths = [_WEBRTC_CHROME, _WEBRTC_EDGE]
|
||||
|
||||
Reference in New Issue
Block a user