-
Notifications
You must be signed in to change notification settings - Fork 0
PL-240 Add harden-host.sh script #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
burakberkkeskin
wants to merge
2
commits into
develop
Choose a base branch
from
add-harden-host-script
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,301 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # harden-host.sh — Security hardening for the PHYSICAL Mac host that runs Tart | ||
| # VMs. Target: macOS Tahoe (26) and later. | ||
| # | ||
| # Threat model: the host is the durable, attackable surface. It should be a | ||
| # trustworthy, compliant endpoint. The build VMs are disposable and relaxed; | ||
| # the HOST is where you keep SIP/FileVault/Gatekeeper on and lock down services. | ||
| # | ||
| # IMPORTANT: This script applies a safe, scriptable SUBSET of CIS Level 1. | ||
| # The authoritative path for a fleet is MDM-delivered configuration profiles | ||
| # generated by the macOS Security Compliance Project (mSCP), tailored to a | ||
| # CIS L1 baseline with SSH deliberately exempted. Use this script for | ||
| # standalone hosts or to bootstrap before MDM enrollment. | ||
| # | ||
| # By default this script AUDITS and applies safe changes. SIP and FileVault | ||
| # cannot be safely toggled from a running script (SIP needs recoveryOS; | ||
| # FileVault needs interactive en/escrow) — they are verified and reported only. | ||
| # | ||
| # USAGE | ||
| # sudo ./harden-host.sh # audit + apply safe changes | ||
| # sudo ./harden-host.sh --audit-only # report posture, change nothing | ||
| # sudo ./harden-host.sh --dry-run # print intended changes, do nothing | ||
| # | ||
| # SSH (Remote Login) is intentionally LEFT ENABLED because CI orchestration | ||
| # usually needs it. Lock it down separately (key-only auth, restricted users). | ||
| # Pass --disable-ssh to turn it off if your orchestrator does not use it. | ||
| # | ||
| set -euo pipefail | ||
|
|
||
| DRY_RUN=0 | ||
| AUDIT_ONLY=0 | ||
| DISABLE_SSH=0 | ||
| LOG_PREFIX="[harden-host]" | ||
|
|
||
| for arg in "$@"; do | ||
| case "$arg" in | ||
| --dry-run) DRY_RUN=1 ;; | ||
| --audit-only) AUDIT_ONLY=1 ;; | ||
| --disable-ssh) DISABLE_SSH=1 ;; | ||
| -h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; | ||
| *) echo "Unknown argument: $arg" >&2; exit 2 ;; | ||
| esac | ||
| done | ||
|
|
||
| if [[ "$(id -u)" -ne 0 ]]; then | ||
| echo "$LOG_PREFIX must run as root (use sudo)." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # The administrator account whose per-user (GUI domain) agents/preferences we | ||
| # harden. Defaults to the invoking sudo user; override with ADMIN_USER=name. | ||
| ADMIN_USER="${ADMIN_USER:-${SUDO_USER:-$(stat -f%Su /dev/console)}}" | ||
| ADMIN_UID="$(id -u "$ADMIN_USER" 2>/dev/null || echo "")" | ||
|
|
||
| # Telemetry / analytics / ad hostnames. ANALYTICS ONLY — safe to sinkhole. We | ||
| # deliberately DO NOT touch ocsp.apple.com, timestamp.apple.com, the notary | ||
| # service, api.apple-cloudkit.com, developer.apple.com, or | ||
| # appstoreconnect.apple.com — those are load-bearing for signing/notarization. | ||
| HOSTS_MARK_BEGIN="# >>> appcircle-telemetry-block >>>" | ||
| HOSTS_MARK_END="# <<< appcircle-telemetry-block <<<" | ||
| TELEMETRY_DOMAINS=( | ||
| metrics.apple.com | ||
| securemetrics.apple.com | ||
| metrics.icloud.com | ||
| metrics.mzstatic.com | ||
| weather-analytics-events.apple.com | ||
| books-analytics-events.apple.com | ||
| iadsdk.apple.com | ||
| api-adservices.apple.com | ||
| supportmetrics.apple.com | ||
| xp.apple.com | ||
| ) | ||
|
|
||
| # System analytics/diagnostics daemons (disabled in system domain). These only | ||
| # collect/submit telemetry — none are required for signing, notarization, or | ||
| # security updates. | ||
| ANALYTICS_DAEMONS=( | ||
| com.apple.analyticsd | ||
| com.apple.osanalytics.osanalyticshelper | ||
| com.apple.SubmitDiagInfo | ||
| com.apple.audioanalyticsd | ||
| com.apple.wifianalyticsd | ||
| com.apple.ecosystemanalyticsd | ||
| com.apple.geoanalyticsd | ||
| ) | ||
|
|
||
| # Per-user agents (disabled in gui/<uid>): Siri / Apple Intelligence / phone-home | ||
| # suggestion + media-analysis agents. Safe to disable on a build host. | ||
| USER_AGENTS=( | ||
| com.apple.assistantd | ||
| com.apple.Siri.agent | ||
| com.apple.siriknowledged | ||
| com.apple.assistant_service | ||
| com.apple.generativeexperiencesd | ||
| com.apple.intelligenceflowd | ||
| com.apple.intelligencecontextd | ||
| com.apple.intelligenceplatformd | ||
| com.apple.knowledge-agent | ||
| com.apple.naturallanguaged | ||
| com.apple.suggestd | ||
| com.apple.parsecd | ||
| com.apple.photoanalysisd | ||
| com.apple.mediaanalysisd | ||
| com.apple.ap.adprivacyd | ||
| com.apple.ap.promotedcontentd | ||
| ) | ||
|
|
||
| log() { echo "$LOG_PREFIX $*"; } | ||
| ok() { echo "$LOG_PREFIX [ OK ] $*"; } | ||
| warn() { echo "$LOG_PREFIX [WARN] $*"; } | ||
| run() { | ||
| if [[ "$DRY_RUN" -eq 1 || "$AUDIT_ONLY" -eq 1 ]]; then | ||
| echo "$LOG_PREFIX WOULD RUN: $*" | ||
| else | ||
| eval "$@" | ||
| fi | ||
| } | ||
| # Run a command as the admin user inside their GUI launchd domain. | ||
| as_user() { | ||
| if [[ -z "$ADMIN_UID" ]]; then | ||
| warn "No admin user resolved; skipping per-user setting: $*" | ||
| return 0 | ||
| fi | ||
| if [[ "$DRY_RUN" -eq 1 || "$AUDIT_ONLY" -eq 1 ]]; then | ||
| echo "$LOG_PREFIX WOULD RUN (as $ADMIN_USER): $*" | ||
| else | ||
| launchctl asuser "$ADMIN_UID" sudo -u "$ADMIN_USER" "$@" || true | ||
| fi | ||
| } | ||
|
|
||
| FW=/usr/libexec/ApplicationFirewall/socketfilterfw | ||
|
|
||
| echo "===========================================================" | ||
| echo " macOS host hardening — $(sw_vers -productName) $(sw_vers -productVersion)" | ||
| echo "===========================================================" | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # SECTION A — Platform protections (verify only; do not toggle from a script) | ||
| # --------------------------------------------------------------------------- | ||
| log "A. Verifying platform protections (these must stay ON)" | ||
|
|
||
| # SIP | ||
| if csrutil status 2>/dev/null | grep -qi "enabled"; then | ||
| ok "System Integrity Protection is enabled" | ||
| else | ||
| warn "SIP is NOT fully enabled. Re-enable from recoveryOS: 'csrutil enable'. A signing host should never run with SIP off." | ||
| fi | ||
|
|
||
| # FileVault | ||
| if fdesetup status 2>/dev/null | grep -qi "FileVault is On"; then | ||
| ok "FileVault is on" | ||
| else | ||
| warn "FileVault is OFF. Enable + escrow recovery key (ideally via MDM). For unattended reboot use 'fdesetup authrestart'." | ||
| fi | ||
|
|
||
| # Gatekeeper | ||
| if spctl --status 2>/dev/null | grep -qi "assessments enabled"; then | ||
| ok "Gatekeeper assessments are enabled" | ||
| else | ||
| warn "Gatekeeper is disabled. Attempting to re-enable..." | ||
| run "spctl --global-enable 2>/dev/null || spctl --master-enable 2>/dev/null || true" | ||
| fi | ||
|
|
||
| # Secure Boot policy (Apple silicon) — report only | ||
| if command -v bputil >/dev/null 2>&1; then | ||
| log "Secure Boot policy (review for 'Full Security'):" | ||
| bputil -d 2>/dev/null | grep -i "security" || true | ||
| fi | ||
|
|
||
| [[ "$AUDIT_ONLY" -eq 1 ]] && { log "Audit-only mode: stopping before applying changes."; exit 0; } | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # SECTION B — Application firewall | ||
| # --------------------------------------------------------------------------- | ||
| log "B. Enabling application firewall + stealth mode" | ||
| run "$FW --setglobalstate on" | ||
| run "$FW --setstealthmode on" | ||
| run "$FW --setallowsigned on" | ||
| run "$FW --setallowsignedapp on" | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # SECTION C — Disable sharing services (CIS 2.3.x) | ||
| # --------------------------------------------------------------------------- | ||
| log "C. Disabling sharing services not needed on a build host" | ||
|
|
||
| # Screen Sharing — intentionally LEFT ENABLED for remote administration of the host. | ||
| warn "Leaving Screen Sharing ENABLED for remote administration. Restrict it to trusted users/networks." | ||
| # Remote Management (ARD) | ||
| run "/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -deactivate -stop 2>/dev/null || true" | ||
| # File Sharing (SMB/AFP) | ||
| run "launchctl disable system/com.apple.smbd 2>/dev/null || true" | ||
| # Printer Sharing | ||
| run "cupsctl --no-share-printers 2>/dev/null || true" | ||
| # Remote Apple Events | ||
| run "systemsetup -setremoteappleevents off 2>/dev/null || true" | ||
| # Internet Sharing | ||
| run "defaults write /Library/Preferences/SystemConfiguration/com.apple.nat NAT -dict Enabled -int 0 2>/dev/null || true" | ||
| # Bluetooth (no use on a rack host) | ||
| run "defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 0 2>/dev/null || true" | ||
|
|
||
| # SSH / Remote Login | ||
| if [[ "$DISABLE_SSH" -eq 1 ]]; then | ||
| log "Disabling Remote Login (SSH) as requested" | ||
| run "systemsetup -setremotelogin off 2>/dev/null || true" | ||
| else | ||
| warn "Leaving Remote Login (SSH) ENABLED for CI access. Harden it: key-only auth, no root login, restricted AllowUsers in /etc/ssh/sshd_config." | ||
| fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # SECTION D — Accounts & login window (CIS 2.11.x / 2.12.x) | ||
| # --------------------------------------------------------------------------- | ||
| log "D. Hardening accounts and login window" | ||
| # Disable guest account | ||
| run "sysadminctl -guestAccount off 2>/dev/null || true" | ||
| run "defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool false" | ||
| # Disable guest access to shared folders | ||
| run "sysadminctl -smbGuestAccess off 2>/dev/null || true" | ||
| # Login window shows name+password fields, not the user list | ||
| run "defaults write /Library/Preferences/com.apple.loginwindow SHOWFULLNAME -bool true" | ||
| # Disable password hints | ||
| run "defaults write /Library/Preferences/com.apple.loginwindow RetriesUntilHint -int 0" | ||
| # Require password immediately after screensaver/sleep | ||
| run "defaults -currentHost write com.apple.screensaver askForPassword -int 1" | ||
| run "defaults -currentHost write com.apple.screensaver askForPasswordDelay -int 0" | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # SECTION F — Privacy / analytics (CIS 2.5.x / 2.6.3.x) | ||
| # --------------------------------------------------------------------------- | ||
| log "F. Disabling analytics sharing and personalized ads" | ||
| run "defaults write /Library/Application\\ Support/CrashReporter/DiagnosticMessagesHistory.plist AutoSubmit -bool false" | ||
| run "defaults write /Library/Application\\ Support/CrashReporter/DiagnosticMessagesHistory.plist ThirdPartyDataSubmit -bool false" | ||
| run "defaults write /Library/Preferences/com.apple.SubmitDiagInfo AutoSubmit -bool false 2>/dev/null || true" | ||
|
|
||
| # Disable system analytics/diagnostics daemons (effective after reboot). None of | ||
| # these are required for signing, notarization, or security updates. | ||
| log "Disabling system analytics/diagnostics daemons" | ||
| for d in "${ANALYTICS_DAEMONS[@]}"; do | ||
| run "launchctl disable system/$d 2>/dev/null || true" | ||
| done | ||
|
|
||
| # Limit ad tracking / personalization for the admin user. | ||
| log "Disabling personalized ads / limiting ad tracking (user: $ADMIN_USER)" | ||
| as_user defaults write com.apple.AdLib allowApplePersonalizedAdvertising -bool false | ||
| as_user defaults write com.apple.AdLib forceLimitAdTracking -bool true | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # SECTION F2 — Siri / Apple Intelligence / phone-home agents | ||
| # --------------------------------------------------------------------------- | ||
| log "F2. Disabling Siri / Apple Intelligence / suggestion agents (user: $ADMIN_USER)" | ||
| if [[ -n "$ADMIN_UID" ]]; then | ||
| for a in "${USER_AGENTS[@]}"; do | ||
| if [[ "$DRY_RUN" -eq 1 || "$AUDIT_ONLY" -eq 1 ]]; then | ||
| echo "$LOG_PREFIX WOULD RUN: launchctl disable gui/$ADMIN_UID/$a" | ||
| else | ||
| launchctl disable "gui/$ADMIN_UID/$a" 2>/dev/null || true | ||
| launchctl bootout "gui/$ADMIN_UID/$a" 2>/dev/null || true | ||
| fi | ||
| done | ||
| else | ||
| warn "No admin user resolved; skipping per-user agent disable." | ||
| fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # SECTION F3 — Telemetry domain sinkhole via /etc/hosts | ||
| # --------------------------------------------------------------------------- | ||
| log "F3. Blocking telemetry/analytics domains in /etc/hosts" | ||
| if [[ "$DRY_RUN" -eq 1 || "$AUDIT_ONLY" -eq 1 ]]; then | ||
| for h in "${TELEMETRY_DOMAINS[@]}"; do echo "$LOG_PREFIX WOULD RUN: block $h"; done | ||
| else | ||
| # Remove any prior block first (idempotent) | ||
| if grep -qF "$HOSTS_MARK_BEGIN" /etc/hosts; then | ||
| sed -i '' "/$(printf '%s' "$HOSTS_MARK_BEGIN" | sed 's/[][\.*^$/]/\\&/g')/,/$(printf '%s' "$HOSTS_MARK_END" | sed 's/[][\.*^$/]/\\&/g')/d" /etc/hosts | ||
| fi | ||
| { | ||
| echo "$HOSTS_MARK_BEGIN" | ||
| for h in "${TELEMETRY_DOMAINS[@]}"; do | ||
| echo "0.0.0.0 $h" | ||
| echo "::1 $h" | ||
| done | ||
| echo "$HOSTS_MARK_END" | ||
| } >> /etc/hosts | ||
| dscacheutil -flushcache 2>/dev/null || true | ||
| killall -HUP mDNSResponder 2>/dev/null || true | ||
| fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # SECTION G — Software update policy | ||
| # --------------------------------------------------------------------------- | ||
| log "G. Software update policy" | ||
| # Keep AUTOMATIC SECURITY responses & XProtect updates ON for the host, but | ||
| # control major macOS update timing. Enforce a minimum and defer window via MDM | ||
| # in production. Here we ensure security data updates stay enabled: | ||
| run "defaults write /Library/Preferences/com.apple.SoftwareUpdate ConfigDataInstall -bool true" | ||
| run "defaults write /Library/Preferences/com.apple.SoftwareUpdate CriticalUpdateInstall -bool true" | ||
|
|
||
| echo "===========================================================" | ||
| log "Host hardening pass complete." | ||
| log "Next: enroll in MDM and push an mSCP-tailored CIS L1 profile for enforced, non-removable settings." | ||
| log "Re-run this script (or re-verify Section A) after any macOS update." | ||
| echo "===========================================================" | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.