Skip to main content

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:

  1. Audit: Check if settings match your baseline requirements
  2. 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 RunsTriggered by MDM or manually
โ†’
๐Ÿ”Check SettingRuns audit command
โ†’
โš–๏ธCompare ResultAgainst expected value
โ†’
โœ…Pass/FailLog result

Script Modesโ€‹

The generated script supports three execution modes:

๐Ÿ”--check (Audit Only)

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.

๐Ÿ”ง--fix (Remediate Only)

Runs all remediation functions to apply correct settings. Use this when you want to bring a device into compliance. Typically run after --check identifies failures.

๐Ÿ”„--cfc (Check-Fix-Check)

Runs audit, then remediation, then audit again. This verifies that fixes were actually applied successfully. The final audit confirms the device is now compliant.

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
# Discussion: Firewall logging MUST be enabled...

audit_os_firewall_log_enable() {
# Check command from the rule YAML
local result=$(/usr/libexec/ApplicationFirewall/socketfilterfw \
--getloggingmode 2>/dev/null | grep -c "Log mode is on")

# Expected result from the rule YAML
local expected="1"

if [[ "$result" == "$expected" ]]; then
logmessage "os_firewall_log_enable: PASSED"
return 0
else
logmessage "os_firewall_log_enable: FAILED (expected: $expected, got: $result)"
return 1
fi
}

fix_os_firewall_log_enable() {
# Fix command from the rule YAML
/usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode on
logmessage "os_firewall_log_enable: Fixed"
}

What's happening here:

  1. The audit_ function runs the check command defined in the rule's YAML
  2. It compares the result against the expected value
  3. Returns pass (0) or fail (1) and logs the result
  4. The fix_ function runs the remediation command to correct the setting

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โ€‹

๐Ÿ“…
MDM triggers inventory collection

On a schedule (e.g., daily) or when you request a device update, your MDM tells the Mac to collect inventory.

๐Ÿ“œ
Mac runs each EA script

The MDM agent executes every extension attribute script on the device.

๐Ÿ“ค
Script outputs a result

Each script prints a value (like "Compliant" or "Non-Compliant") in a format the MDM expects.

๐Ÿ“Š
MDM stores the value

The result appears in the device's inventory record. You can search, report, and create smart groups based on these values.

Example Extension Attributeโ€‹

#!/bin/zsh
# Extension Attribute: Firewall Logging
# Checks if firewall logging is enabled

result=$(/usr/libexec/ApplicationFirewall/socketfilterfw \
--getloggingmode 2>/dev/null | grep -c "Log mode is on")

if [[ "$result" == "1" ]]; then
echo "<result>Compliant</result>"
else
echo "<result>Non-Compliant</result>"
fi

The output format matters: Different MDMs expect different formats:

  • Jamf Pro: <result>value</result>
  • Kandji: Plain text output
  • Mosyle: Plain text output
  • Intune: Specific schema requirements

MACE generates EAs in the correct format for each supported MDM.

Deploying Scripts via MDMโ€‹

Jamf Proโ€‹

๐Ÿ” Deploying the Audit Script

1
Go to Settings โ†’ Scripts

In Jamf Pro, navigate to Settings โ†’ Computer Management โ†’ Scripts.

2
Create a new script

Click "New" and paste the contents of your compliance script. Set the script to run as the logged-in user or as root (most compliance checks need root).

3
Set script parameters

Add a parameter for the mode: $4 can be --check, --fix, or --cfc.

4
Create a Policy

Go to Computers โ†’ Policies โ†’ New. Add your script and set the parameter to --check for audit-only runs.

5
Set trigger and scope

Choose when to run: Recurring Check-in (daily/weekly), Startup, or manual trigger. Scope to your target computers.

๐Ÿ“Š Deploying Extension Attributes

1
Go to Settings โ†’ Extension Attributes

In Jamf Pro, navigate to Settings โ†’ Computer Management โ†’ Extension Attributes.

2
Create a new Extension Attribute

Click "New" and set Data Type to "String". Set Input Type to "Script".

3
Paste the EA script

Copy the contents of the extension attribute script (e.g., ea_os_firewall_log_enable.sh) into the script field.

4
Save and wait for inventory

The EA runs during the next inventory collection. You can force this by running sudo jamf recon on a test Mac.

5
Create Smart Groups (optional)

Create a Smart Computer Group where the EA equals "Non-Compliant" to identify devices needing remediation.

Microsoft Intuneโ€‹

๐Ÿ“œ Deploying Scripts in Intune

1
Go to Devices โ†’ macOS โ†’ Shell scripts

In the Intune admin center, navigate to Devices โ†’ macOS โ†’ Shell scripts.

2
Add a new script

Click "Add" and upload your compliance script file.

3
Configure script settings

Set "Run script as signed-in user" to No (runs as root). Set "Hide script notifications" based on your preference.

4
Set script arguments

In "Script arguments", enter --check for audit or --fix for remediation.

5
Assign to groups

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:

๐ŸŸ KandjiUse Custom Scripts library item. Set to run on a schedule or once.
๐ŸŸฃMosyleUse Custom Commands. Upload script and assign to device groups.
โšซWorkspace ONEUse Scripts feature. Configure trigger and assignment.

What Happens on the Macโ€‹

When your MDM runs the compliance script, here's the sequence:

๐Ÿ“ฅ
MDM agent receives the command

The MDM agent on the Mac (like Jamf binary or Intune agent) receives instruction to run the script.

๐Ÿ”
Script runs with root privileges

Most compliance checks need root access to read system settings. The MDM agent runs the script as root.

๐Ÿ”
Audit functions execute

Each rule's check command runs. Commands like defaults read, systemsetup, or csrutil status query current settings.

๐Ÿ“
Results are logged

Pass/fail results write to /Library/Logs/ and stdout. Your MDM captures this output.

๐Ÿ”ง
Fix functions run (if --fix or --cfc)

Remediation commands execute to change settings. This might run defaults write, enable services, or modify system files.

๐Ÿ“ค
Exit code returned to MDM

The script returns 0 (success) or non-zero (failures detected). Some MDMs use this to determine policy success.

Audit Plist and Exemptionsโ€‹

The compliance script checks for an "audit plist" file that tracks exemptions. If a rule is marked exempt, the script skips it.

How Exemptions Workโ€‹

# The script checks the audit plist before running each rule
exempt_reason=$(defaults read "/Library/Preferences/org.{baseline}.audit.plist" \
os_firewall_log_enable_exempt_reason 2>/dev/null)

if [[ -n "$exempt_reason" ]]; then
logmessage "os_firewall_log_enable: EXEMPT - $exempt_reason"
return 0 # Skip this rule
fi

Why use exemptions?

  • A device has a legitimate reason to deviate (for example, testing system or specific application requirement)
  • Document the reason in the audit plist so auditors can see why the rule was skipped

Deploying the Audit Plistโ€‹

The audit plist can be deployed as:

  • A .plist file copied to /Library/Preferences/
  • A .mobileconfig profile pushed via MDM (recommended because it ensures the file is managed)

Best Practicesโ€‹

๐Ÿงช
Test on pilot devices first

Run the script manually on test Macs before deploying via MDM. Verify the checks work correctly and fixes don't break anything.

๐Ÿ”
Start with audit only

Deploy with --check first to understand your current compliance posture. Review results before running --fix.

๐Ÿ“…
Schedule regular audits

Run the audit script on a recurring schedule (daily or weekly) to continuously monitor compliance status.

๐ŸŽฏ
Use Smart Groups for remediation

Create MDM groups based on EA results. Scope remediation policies only to non-compliant devices.

๐Ÿ“Š
Monitor for drift

Settings can change over time. Regular audits catch when devices drift out of compliance.