Scripts

Shell scripts that audit compliance status and remediate non-compliant settings.
Deploy via MDM to continuously monitor and enforce your security baseline.
What Are Compliance Scripts?โ
Compliance scripts are shell scripts (.sh files) that run directly on a Mac. They do two things:
- Audit: Check if settings match your baseline requirements
- Remediate: Change settings to match requirements if they don't
Unlike configuration profiles (which declare settings for macOS to enforce), scripts actively execute commands. This makes them more flexible. They can check anything you can query via command line, and fix anything you can change via command line.
How the Compliance Script Worksโ
MACE can generate either a single combined compliance script or individual scripts per rule (configured in Build Options). The combined script contains functions for every enabled rule. Here's what happens when it runs:
Script Modesโ
The generated script has three run modes, plus a set of single-rule, reporting, and exemption flags. It must run as root, and with no arguments in a terminal it opens an interactive menu.
Runs all audit functions and reports pass/fail for each rule. Does NOT change any settings. Use this to assess current compliance status without making changes.
Remediates the rules the last scan recorded as failing; already-compliant rules are skipped. It needs an existing scan, so run --check first or use --cfc. Exempt rules are never fixed.
Runs audit, then remediation, then audit again. This verifies that fixes were actually applied successfully. The final audit confirms the device is now compliant.
| ๐ | Results: --stats (one-line summary for MDMs), --compliant, --non_compliant, --json |
| ๐ฏ | Single rules: --list, --check-rule <id>, --fix-rule <id>, and --dry-run to show what a fix would run |
| โ | Exemptions: --exempt <id> <reason> (a reason is required), --unexempt <id>, --exemptions |
| ๐งน | Housekeeping: --reset and --reset-all clear stored results without touching exemptions, --reset-ssh, --quiet=1|2, --version, --help |
| โ๏ธ | Settings block at the top of the script: FORCED_MODE, RESET_RESULTS, DRY_RUN, QUIET_LEVEL, RESET_SSH_BEFORE_FIX. A compliance_args array in the org.<baseline>.audit domain can supply arguments from MDM and overrides FORCED_MODE |
Mode precedence is FORCED_MODE, then Jamf's $4, then the first argument. Individual per-rule scripts only accept --check and --fix.
Example: What a Rule Looks Like in the Scriptโ
For a rule like "Enable Firewall Logging", the script contains:
# Rule: os_firewall_log_enable
# STIG ID: APPL-15-005001
# Severity: medium
audit_os_firewall_log_enable() {
# Check command and expected result from the rule YAML
check_command='/usr/libexec/ApplicationFirewall/socketfilterfw --getloggingmode 2>/dev/null | grep -c "Log mode is on"'
audit_rule "os_firewall_log_enable" "$check_command" "1"
}
fix_os_firewall_log_enable() {
# Fix command from the rule YAML
rule_fix "os_firewall_log_enable" '/usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode on'
}
What's happening here:
- The
audit_function hands the rule's check command and expected value to the sharedaudit_rulehelper - The helper runs the command, compares the result, and records the rule as passed, failed, or not applicable
- A line like
[INFO] [timestamp] os_firewall_log_enable failed (Result: 0, Expected: "1")goes to the log, and an exempt failure is recorded as an allowed exemption with its reason - In fix mode, the
fix_function runs the remediation only if the last scan recorded a failure and the rule isn't exempt
Extension Attributesโ
Extension attributes (EAs) are individual scripts that report a single value back to your MDM. They're designed for MDM inventory collection.
How Extension Attributes Workโ
On a schedule (e.g., daily) or when you request a device update, your MDM tells the Mac to collect inventory.
The MDM agent executes every extension attribute script on the device.
Each script prints a value (like "Compliant" or "Non-Compliant") in a format the MDM expects.
The result appears in the device's inventory record. You can search, report, and create smart groups based on these values.
Example Extension Attributeโ
MACE writes one reporting script per MDM per baseline into build/extension_attributes/, not one per rule. Each one reads the results the compliance script left in /Library/Preferences/<baseline>_audit.plist and reports the failed rule IDs:
#!/bin/zsh
# jamf_cis_lvl2_extension_attribute.sh
# Reports the rules that failed the last compliance scan
failed=$(read_failed_rules_from_audit_plist)
if [[ -z "$scan_data" ]]; then
echo "<result>No Scan Data</result>"
elif [[ -z "$failed" ]]; then
echo "<result>Compliant</result>"
else
echo "<result>$failed</result>"
fi
What each MDM gets:
- Jamf Pro:
jamf_<baseline>_extension_attribute.shwith<result>โฆ</result>output:Compliant,No Scan Data, or the failed rule IDs - Intune, Workspace ONE, Addigy: one plain-text line such as
Non-Compliant (3) - Failed Rules: a,b,c. Workspace ONE gets both a sensor (ws1_โฆ) and a custom attribute (ws1ca_โฆ); Intune also gets a compliance discovery script and rules JSON for custom compliance - Iru:
iru_โฆcustom attribute - Fleet:
fleet_<baseline>_policies.sql, osquery policies to paste into Fleet
A README in the folder walks through the setup for each. The scripts only report; the compliance script has to be running on the Mac for them to have data.
Deploying Scripts via MDMโ
Jamf Proโ
๐ Deploying the Audit Script
In Jamf Pro, navigate to Settings โ Computer Management โ Scripts.
Click "New" and paste the contents of your compliance script. Jamf policy scripts run as root, which the script requires.
Add a parameter for the mode: $4 can be --check, --fix, or --cfc.
Go to Computers โ Policies โ New. Add your script and set the parameter to --check for audit-only runs.
Choose when to run: Recurring Check-in (daily/weekly), Startup, or manual trigger. Scope to your target computers.
๐ Deploying Extension Attributes
In Jamf Pro, navigate to Settings โ Computer Management โ Extension Attributes.
Click "New" and set Data Type to "String". Set Input Type to "Script".
Copy the contents of jamf_<baseline>_extension_attribute.sh from build/extension_attributes/ into the script field.
The EA runs during the next inventory collection. You can force this by running sudo jamf recon on a test Mac.
Create a Smart Computer Group where the EA is not "Compliant" and is not "No Scan Data" to identify devices needing remediation. MACE's Jamf upload can create these groups for you.
Microsoft Intuneโ
๐ Deploying Scripts in Intune
In the Intune admin center, navigate to Devices โ macOS โ Shell scripts.
Click "Add" and upload your compliance script file.
Set "Run script as signed-in user" to No (runs as root). Set "Hide script notifications" based on your preference.
Intune macOS shell scripts take no arguments, so set Forced Mode in the Build Hub (or edit FORCED_MODE at the top of the script) to --check, --fix, or --cfc before uploading.
Assign the script to device groups. Set the run frequency (once, every day, etc.).
Other MDMsโ
Scripts work with any MDM that supports running shell scripts on macOS:
| ๐ฃ | Iru | Use Custom Scripts library item. Set to run on a schedule or once. |
| โซ | Workspace ONE | Use Scripts feature. Configure trigger and assignment. |
| ๐ค | Addigy | Use a Custom Software item or script policy to run on a schedule or once. |
| ๐ด | Fleet | Use Scripts to run shell scripts on enrolled Macs, on demand or via policy automation. |
What Happens on the Macโ
When your MDM runs the compliance script, here's the sequence:
The MDM agent on the Mac (like Jamf binary or Intune agent) receives instruction to run the script.
Most compliance checks need root access to read system settings. The MDM agent runs the script as root.
Each rule's check command runs. Commands like defaults read, systemsetup, or csrutil status query current settings.
Results go to /Library/Logs/<hostname>_<baseline>_baseline.log and a matching .csv, failures to /Library/Logs/<baseline>_failures.log, per-rule status to /Library/Preferences/<baseline>_audit.plist, and a summary to the unified log and stdout. Your MDM captures the stdout.
Remediation commands execute to change settings. This might run defaults write, enable services, or modify system files.
The script exits 0 when it ran, 2 for a usage error or unknown flag, and 1 if it isn't root, isn't zsh, or --fix was run before any scan. Failed rules don't change the exit code; read the summary line, the audit plist, or the reporting scripts for that. Run from an MDM with no mode and no terminal, it prints usage and exits 0 without scanning, so always set a mode.
Audit Preferences and Exemptionsโ
The compliance script reads an audit preferences domain, org.<baseline>.audit, that holds one entry per rule. An exempt rule is still checked, but a failure is logged as an allowed exemption, kept out of the failures log, and never remediated.
How Exemptions Workโ
<!-- org.<baseline>.audit: one dictionary per rule -->
<key>os_firewall_log_enable</key>
<dict>
<key>exempt</key><true/>
<key>exempt_reason</key><string>Logging handled by the SIEM agent</string>
</dict>
The script looks for exemptions in three places, in order: user-scoped managed preferences, device managed preferences, then the local /Library/Preferences/org.<baseline>.audit.plist. Results are stored separately in /Library/Preferences/<baseline>_audit.plist.
Where exemptions come from:
- The project. Rules you mark exempt in the Compliance Editor are pre-set with their reasons in the audit preferences MACE builds. See Exempting Rules.
- The Mac. An admin can add one locally with
sudo ./script.sh --exempt <rule_id> "reason", remove it with--unexempt, and list them with--exemptions. The interactive menu has the same options. - Jamf's editor. With Write JSON Schema instead on, Jamf Pro admins manage exemptions from the Custom Settings UI.
Deploying the Audit Preferencesโ
The audit preferences file can be deployed as:
- A
.mobileconfigprofile pushed via MDM (recommended, since managed preferences are read first and can't be edited on the Mac) - A
.plistfile copied to/Library/Preferences/ - An Intune XML preference file or a Jamf JSON schema, depending on the Format you build with
Exempting a rule in the project only takes effect on a Mac once the updated audit preferences land there. Profiles already deployed keep enforcing the setting until they're re-uploaded.
Best Practicesโ
Run the script manually on test Macs before deploying via MDM. Verify the checks work correctly and fixes don't break anything.
Deploy with --check first to understand your current compliance posture. Review results before running --fix.
Run the audit script on a recurring schedule (daily or weekly) to continuously monitor compliance status.
Create MDM groups based on EA results. Scope remediation policies only to non-compliant devices.
Settings can change over time. Regular audits catch when devices drift out of compliance.