Mac Health Check (5.0.0b6)
Pre-release
Pre-release
30-Sep-2026
- Fixed the detached Preset 6 Inspect summary never displaying in
Self Service:/Library/Managementcan be0700root, 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
0755tree at/Library/Application Support/${reverseDomainNameNotation}/Inspect(config and compliance plist) with per-user control files underInspect/Users/<user>; the canonical report, secrets and caches remain root-only inorganizationDirectory - Removes root-owned 5.0.0-beta Inspect leftovers from
organizationDirectory
- Moved user-facing Inspect assets to a root-owned
- 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 JSONoptionsblock (moveable,ontop,windowbuttons: "min"), replacing an ignored top-levelmoveablekey and adding a minimise button to the detached summary;--ontop --moveablelaunch flags remain for older swiftDialog builds
- Updated the generated Preset 6 Inspect config to declare window options through swiftDialog
- 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 Serviceremediation 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 runComputer 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, preventingStatus Unknownresults when AirPlay Receiver preferences are absent - Fixed
Battery Cycle Countreporting=on macOS 27, whereioregprefixes theCycleCountline with a tree marker - Suppressed
mdmclient AvailableOSUpdatesstderr, which macOS 27 rejects as an unrecognized command, soSilentproduction logs no longer capture themdmclientusage banner - The detached Preset 6 Inspect summary now sets
DIALOG_DEBUG=1only inDebugmode - Added
checkClockSkew()to Jamf Pro runs to detect local clock offset againsttime.apple.combefore 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.zshso generated wrappers decode into a root-onlymktemp -ddirectory, run/bin/zsh --no-rcswith all forwarded arguments (Jamf Pro Parameters 1-11) and remove the copy on exit, replacing the fixed, pre-plantable/var/tmp/MHC.zshpath; removed the--targetoption 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 (newisTrustedRootPath())- Added an optional root-only secrets file,
/Library/Management/org.churchofjesuschrist/MacHealthCheck-Secrets.plist(splunkHECToken,webhookURL;root:wheelmode600), 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 TestandDevelopmentruns now writeMacHealthCheck-Report-<mode>.jsoninstead of the canonical report, skip Splunk HEC delivery and never install the Client-Side Cache copy; cached uploads and targeted rechecks reject canonical reports whosemetadata.operationModeis notSelf ServiceorSilent- Removed
/usr/local/binfrom the script and LaunchDaemonPATH; swiftDialog now runs fromDialog.app/Contents/MacOS/dialogcli, Jamf Pro from/usr/local/jamf/bin/jamf, andjqfrom/usr/bin/jqor a root-owned install only (user-owned Homebrewjqis rejected) - Parameter 6
splunkOperationModenow fails safe: onlyoff,testorproduction(case-insensitive) are accepted, and any unrecognized value falls back totestwith an[ERROR]log entry instead of silently enabling production Splunk HEC delivery - The
/var/tmp/MacHealthCheck-Force-Fresh-Runtrigger 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 rootAppleLocalevalues on exit instead of permanently settingen_US; locales changed by earlier versions are not restored automatically- Pinned Semgrep to
1.177.0in.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, soSilent+splunkOperationMode=productionexits1); deployMacHealthCheck-Secrets.plistinstead, or set the new source-levelallowParameterSecrets="true"as a temporary, not-recommended legacy opt-in Resources/Makefilenow installs the package payload to root-owned/Library/Management/org.churchofjesuschrist/Mac-Health-Check.zshinstead of user-writable/usr/local/bin/Mac-Health-Check, andResources/postInstall.zshruns it with/bin/zsh --no-rcsinSelf Servicemode- Removed
/usr/local/binfromPATHin 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 HTTP5xxor000only); rejected deliveries log a[WARNING]with the HTTP status and a short response excerpt instead of only thecurlexit code; webhook failures do not change report status or exit codes Microsoft Defender Check.sh(0.0.3) now callsmdatpfrom/Applications/Microsoft Defender.app/Contents/Resources/Tools/mdatpinstead of user-writable/usr/local/bin/mdatp
- Refactored
Targeted Post-remediation Verification
Initial (full) run
Second run
Third run
Memory Pressure
Insufficient Data
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
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
}