Skip to content

Mac Health Check (5.0.0b6)

Pre-release
Pre-release

Choose a tag to compare

@dan-snelson dan-snelson released this 01 Oct 12:08
· 0 commits to main since this release
v5.0.0b6
f50bb4f

30-Sep-2026

  • Fixed the detached Preset 6 Inspect summary never displaying in Self Service: /Library/Management can be 0700 root, which blocked the logged-in user from reading the Inspect config and compliance plist and from writing the launch log, while the script still logged a successful launch
    • Moved user-facing Inspect assets to a root-owned 0755 tree at /Library/Application Support/${reverseDomainNameNotation}/Inspect (config and compliance plist) with per-user control files under Inspect/Users/<user>; the canonical report, secrets and caches remain root-only in organizationDirectory
    • Removes root-owned 5.0.0-beta Inspect leftovers from organizationDirectory
  • Raised the minimum required swiftDialog version to 3.1.1.4997
    • Updated the generated Preset 6 Inspect config to declare window options through swiftDialog 3.1.1.4997's JSON options block (moveable, ontop, windowbuttons: "min"), replacing an ignored top-level moveable key and adding a minimise button to the detached summary; --ontop --moveable launch flags remain for older swiftDialog builds
  • Added warning-only Memory Pressure history for full health-check runs, nightly Silent refreshes, and targeted memory-pressure rechecks, with a root-only 14-day JSON Lines history and a two-distinct-day pattern threshold over seven days; cached uploads and replay retain their original observations
  • Added targeted Self Service remediation verification for Issue #103: valid non-healthy reports with a full-run baseline under 36 hours now rerun only affected stable check keys, merge results into the canonical full-state report with per-check timestamps, and fall back safely to a full run when validation fails; Jamf Pro targeted rechecks also run Computer Inventory, and targeted webhook messages are sent only when a rechecked status changes
  • Refactored checkAirPlayReceiver() to recognize macOS 27's new missing-key response and enabled-by-default behavior, preventing Status Unknown results when AirPlay Receiver preferences are absent
  • Fixed Battery Cycle Count reporting = on macOS 27, where ioreg prefixes the CycleCount line with a tree marker
  • Suppressed mdmclient AvailableOSUpdates stderr, which macOS 27 rejects as an unrecognized command, so Silent production logs no longer capture the mdmclient usage banner
  • The detached Preset 6 Inspect summary now sets DIALOG_DEBUG=1 only in Debug mode
  • Added checkClockSkew() to Jamf Pro runs to detect local clock offset against time.apple.com before inventory submission and flag skew above 5 minutes
  • Introduced a dedicated AI Skill to assist Mac Admins with custom deployment
  • Hardened code based on Monocle findings
    • Refactored Resources/createSelfExtracting.zsh so generated wrappers decode into a root-only mktemp -d directory, run /bin/zsh --no-rcs with all forwarded arguments (Jamf Pro Parameters 1-11) and remove the copy on exit, replacing the fixed, pre-plantable /var/tmp/MHC.zsh path; removed the --target option
    • installClientSideScript() now copies the running script into the root LaunchDaemon's path only when it is a root-owned file whose parent directories are root-owned and not group- or world-writable (new isTrustedRootPath())
    • Added an optional root-only secrets file, /Library/Management/org.churchofjesuschrist/MacHealthCheck-Secrets.plist (splunkHECToken, webhookURL; root:wheel mode 600), which takes precedence over Parameters 5 and 8 so those secrets can stay out of the process list; each run logs the secret source and warns when parameters are used; corrected the README claim that parameters never appear in the process list
    • Test and Development runs now write MacHealthCheck-Report-<mode>.json instead of the canonical report, skip Splunk HEC delivery and never install the Client-Side Cache copy; cached uploads and targeted rechecks reject canonical reports whose metadata.operationMode is not Self Service or Silent
    • Removed /usr/local/bin from the script and LaunchDaemon PATH; swiftDialog now runs from Dialog.app/Contents/MacOS/dialogcli, Jamf Pro from /usr/local/jamf/bin/jamf, and jq from /usr/bin/jq or a root-owned install only (user-owned Homebrew jq is rejected)
    • Parameter 6 splunkOperationMode now fails safe: only off, test or production (case-insensitive) are accepted, and any unrecognized value falls back to test with an [ERROR] log entry instead of silently enabling production Splunk HEC delivery
    • The /var/tmp/MacHealthCheck-Force-Fresh-Run trigger is honored only when root-owned; triggers created by other users are ignored, logged and removed
    • CrowdStrike Falcon Status.bash (0.0.14) now restores (or removes) the system and root AppleLocale values on exit instead of permanently setting en_US; locales changed by earlier versions are not restored automatically
    • Pinned Semgrep to 1.177.0 in .github/workflows/security-scan.yml
    • Reporting secrets now fail closed: a Splunk HEC token or webhook URL supplied only through Parameters 8 or 5 is rejected and logged as [ERROR] (Splunk HEC delivery and webhook messages are skipped, so Silent + splunkOperationMode=production exits 1); deploy MacHealthCheck-Secrets.plist instead, or set the new source-level allowParameterSecrets="true" as a temporary, not-recommended legacy opt-in
    • Resources/Makefile now installs the package payload to root-owned /Library/Management/org.churchofjesuschrist/Mac-Health-Check.zsh instead of user-writable /usr/local/bin/Mac-Health-Check, and Resources/postInstall.zsh runs it with /bin/zsh --no-rcs in Self Service mode
    • Removed /usr/local/bin from PATH in the BeyondTrust (0.0.5), Cisco Umbrella (0.0.8), CrowdStrike Falcon (0.0.15), GlobalProtect (0.0.4), Nessus Agent, Splunk Universal Forwarder and Zscaler Tunnel external checks
    • Slack and Microsoft Teams webhook messages now use a shared sendWebhookPayload() sender matching the Splunk HEC pattern (--fail-with-body, --max-time 15, HTTP status capture, up to three attempts with backoff for HTTP 5xx or 000 only); rejected deliveries log a [WARNING] with the HTTP status and a short response excerpt instead of only the curl exit code; webhook failures do not change report status or exit codes
    • Microsoft Defender Check.sh (0.0.3) now calls mdatp from /Applications/Microsoft Defender.app/Contents/Resources/Tools/mdatp instead of user-writable /usr/local/bin/mdatp

Targeted Post-remediation Verification

Initial (full) run

Screenshot 2026-09-22 at 3 47 20 AM

Second run

Screenshot 2026-09-22 at 3 48 01 AM

Third run

Screenshot 2026-09-22 at 3 48 54 AM

Memory Pressure

Insufficient Data

Screenshot 2026-09-25 at 9 06 31 PM
jq . /Library/Management/org.churchofjesuschrist/MacHealthCheck-MemoryPressure-History.jsonl
{
  "timestamp": "2026-09-25T20:38:13-05:00",
  "timestampEpoch": 1790386693,
  "sampleDate": "2026-09-25",
  "hostname": "XDT867503.local",
  "scriptVersion": "4.2.0b5",
  "pressureLevel": "green",
  "freeMemoryPercentage": 89,
  "swapUsedHuman": "0.00M",
  "swapUsedBytes": 0
}

No recurring pressure

Screenshot 2026-09-26 at 7 27 39 AM
jq . /Library/Management/org.churchofjesuschrist/MacHealthCheck-MemoryPressure-History.jsonl
{
  "timestamp": "2026-09-25T20:38:13-05:00",
  "timestampEpoch": 1790386693,
  "sampleDate": "2026-09-25",
  "hostname": "XDT867503.local",
  "scriptVersion": "4.2.0b5",
  "pressureLevel": "green",
  "freeMemoryPercentage": 89,
  "swapUsedHuman": "0.00M",
  "swapUsedBytes": 0
}
{
  "timestamp": "2026-09-25T21:05:43-05:00",
  "timestampEpoch": 1790388343,
  "sampleDate": "2026-09-25",
  "hostname": "XDT867503.local",
  "scriptVersion": "4.2.0b5",
  "pressureLevel": "green",
  "freeMemoryPercentage": 88,
  "swapUsedHuman": "0.00M",
  "swapUsedBytes": 0
}
{
  "timestamp": "2026-09-26T06:55:15-05:00",
  "timestampEpoch": 1790423715,
  "sampleDate": "2026-09-26",
  "hostname": "XDT867503.local",
  "scriptVersion": "4.2.0b5",
  "pressureLevel": "green",
  "freeMemoryPercentage": 94,
  "swapUsedHuman": "0.00M",
  "swapUsedBytes": 0
}