Modified JAMF Compliance Editor Extension: List failed items NOT in exemption list
macOS Compliance Project + JAMF Compliance Editor
As promised I am continuing to look for ways to build out my JAMF Github Repo. One of the items that I have been working heavily with in my role is the macOS Compliance Project and as I am a JAMF administrator that means leveraging the JAMF Compliance Editor. The JAMF Compliance Editor gives you the ability to rapidly configure, tailor and deploy a custom baseline with the macOS Compliance Project.
If you are new to either the macOS Compliance Project or JAMF Compliance Editor, I would recommend watching and reading the following videos and blog posts on the topic.
- NIST macOS Security & JAMF Compliance Review
- NIST macos security How-To
- 2023 JNUC Presentation on JAMF Compliance Editor
Lets assume for the sake of this blog post that you are familiar with both of these wonderful solutions to baseline compliance on macOS.
JAMF Compliance Editor
When you use the JAMF Compliance Editor and you build a baseline it comes with a set of premade scripts that is to be used as a Computer Extension in JAMF.
- compliance-FailedResultsList.sh: A script that will return a list of failed tests that are not passing the baseline test.
- compliance-exemptions.sh: A script that reads the preference file for exemptions that you may have set locally on each machine.
- compliance-FailedResultsCount.sh: A script that counts the number of failed tests. Useful for creating smart groups or reporting on non compliant devices.
- compliance-version.sh: A script that lists the version of the baseline that you are testing for on a specific machine.
Each of these does what it says well. However they, in my mind, have one flaw. They do not take account for each other. For example if you have no exemptions for your baseline then these will work entirely well.
However if you do have exemptions you likely will not want to list the failed tests that are also no longer in scope.
Computer Extension: Failed Results List
Lets focus on compliance-FailedResultsList.sh
The point of the file is to loop through and list all the tests that your baseline has failed on for each computer and display it on the users computer record. This is great and serves an important function. You can now attest to an auditor that you are passing or failing a test and you can prove that you are actively testing and flagging the test results from JAMF.
This is important because its critical that you be aware when a computer falls out of compliance. The entire point of this extension is that you can use it to create smart groups and alerts to ensure you are aware when a computer is no longer in a compliant state.
Here is a copy of the script in question.
#!/bin/bash
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Copyright (c) 2022 Jamf. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Jamf nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY JAMF SOFTWARE, LLC "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL JAMF SOFTWARE, LLC BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
######
# INSTRUCTIONS
# This Jamf Extension Attribute is used in conjunction with the macOS Security Compliance project (mSCP)
# https://github.com/usnistgov/macos_security
#
# Upload the following text into Jamf Pro Extension Attribute section.
#
# Used to gather the list of failed controls from the compliance audit.
######
audit=$(/bin/ls -l /Library/Preferences | /usr/bin/grep 'org.*.audit.plist' | /usr/bin/awk '{print $NF}')
FAILED_RULES=()
if [[ ! -z "$audit" ]]; then
count=$(echo "$audit" | /usr/bin/wc -l | /usr/bin/xargs)
if [[ "$count" == 1 ]]; then
auditfile="/Library/Preferences/${audit}"
rules=($(/usr/libexec/PlistBuddy -c "print :" "${auditfile}" | /usr/bin/awk '/Dict/ { print $1 }'))
for rule in ${rules[*]}; do
if [[ $rule == "Dict" ]]; then
continue
fi
FINDING=$(/usr/libexec/PlistBuddy -c "print :$rule:finding" "${auditfile}")
if [[ "$FINDING" == "true" ]]; then
FAILED_RULES+=($rule)
fi
done
else
FAILED_RULES="Multiple Baselines Found"
fi
else
FAILED_RULES="No Baseline Set"
fi
# sort the results
IFS=$'
' sorted=($(/usr/bin/sort <<<"${FAILED_RULES[*]}")); unset IFS
printf "<result>"
printf "%s
" "${sorted[@]}"
printf "</result>"What about tailoring?
For many, the out of the box restrictions are not acceptable to apply to their fleet in its entirety. For example CMMC requires the use of CAC (Common Access Cards) Cards for authentication. This is just not feasible for many small businesses so we create an exemption.
I wrote a blog entirely on how to create exemptions using JAMF Pro and the macOS Compliance Project, but lets assume again that you have a few exemptions set. The blog post I linked to prior has a good documented workflow for setting up exemptions.
For now when using the script above to report on failed tests, it will return failed tests and list them, even those that are in the exemptions list. That is less than ideal when trying to show an auditor that you are passing all in scope tests.
A novice auditor not familar with the project, or JAMF in general will simply see failed tests here and may have cause to mark them as an active finding even though they are technically out of scope and even though technically they are listed in the exemption section of the users computer record.
I decided to modify the script provided by JAMF to only list failed tests in scope and not in the exemptions list. This is below and you can also find this on my Github repo.
#!/bin/bash
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Copyright (c) 2022 Jamf. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Jamf nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY JAMF SOFTWARE, LLC "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL JAMF SOFTWARE, LLC BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
######
# INSTRUCTIONS
# This Jamf Extension Attribute is used in conjunction with the macOS Security Compliance project (mSCP)
# https://github.com/usnistgov/macos_security
#
# Upload the following text into Jamf Pro Extension Attribute section.
#
# Used to gather the list of failed controls from the compliance audit.
#
#
#
# Modified by Jon Brown for the purposes of showing only the failed results that are in scope and not listing
# any of the failed items listed as exemptions in the macOS Compliance Project.
# Use at your own risk.
######
audit=$(/bin/ls -l /Library/Preferences | /usr/bin/grep 'org.*.audit.plist' | /usr/bin/awk '{print $NF}')
FAILED_RULES=()
EXEMPT_RULES=()
if [[ ! -z "$audit" ]]; then
count=$(echo "$audit" | /usr/bin/wc -l | /usr/bin/xargs)
if [[ "$count" == 1 ]]; then
auditfile1="/Library/Preferences/${audit}"
auditfile2="/Library/Managed Preferences/${audit}"
if [[ ! -e "$auditfile2" ]]; then
auditfile2="/Library/Preferences/${audit}"
fi
# Process FAILED_RULES
rules1=($(/usr/libexec/PlistBuddy -c "print :" "${auditfile1}" | /usr/bin/awk '/Dict/ { print $1 }'))
for rule in ${rules1[*]}; do
if [[ $rule == "Dict" ]]; then
continue
fi
FINDING=$(/usr/libexec/PlistBuddy -c "print :$rule:finding" "${auditfile1}")
if [[ "$FINDING" == "true" ]]; then
FAILED_RULES+=($rule)
fi
done
# Process EXEMPT_RULES
rules2=($(/usr/libexec/PlistBuddy -c "print :" "${auditfile2}" | /usr/bin/awk '/Dict/ { print $1 }'))
for rule in ${rules2[*]}; do
if [[ $rule == "Dict" ]]; then
continue
fi
exemptions=$(/usr/libexec/PlistBuddy -c "print :$rule:exempt" "${auditfile2}" 2>/dev/null)
if [[ "$exemptions" == "true" ]]; then
EXEMPT_RULES+=($rule)
fi
done
else
FAILED_RULES=("Multiple Baselines Found")
EXEMPT_RULES=("Multiple Baselines Found")
fi
else
FAILED_RULES=("No Baseline Set")
EXEMPT_RULES=("No Baseline Set")
fi
if [[ ${#EXEMPT_RULES[@]} == 0 ]]; then
EXEMPT_RULES=("No Exemptions Set")
fi
# Remove items from FAILED_RULES that are in EXEMPT_RULES
filtered_failed_rules=()
for rule in "${FAILED_RULES[@]}"; do
if [[ ! " ${EXEMPT_RULES[@]} " =~ " ${rule} " ]]; then
filtered_failed_rules+=("$rule")
fi
done
# Sort the results
IFS=$'\n' sorted=($(/usr/bin/sort <<<"${filtered_failed_rules[*]}")); unset IFS
printf "<result>"
printf "%s\n" "${sorted[@]}"
printf "</result>"Conclusion
With this updated version I can now show an auditor that while we do have exemptions we are passing all in scope tests in JAMF Pro for an easier attestation process.
If you found this post useful, Follow me and comment with questions, or feedback. As always here are the sources I referenced throughout this blog post.
Sources
- How to create exemptions using JAMF Pro
- JAMF Github Repo
- NIST macOS Security & JAMF Compliance Review
- NIST macos security How-To
- 2023 JNUC Presentation on JAMF Compliance Editor
AI Usage Transparency Report
AI Era · Written during widespread use of AI tools
AI Signal Composition
Score: 0.35 · Moderate AI Influence
Summary
The script provided by JAMF does not take into account exemptions when listing failed tests, which can lead to incorrect findings for auditors.
Related Posts
Scoring AI Influence in Jekyll Posts with Local LLMs
There’s a moment that kind of sneaks up on you when you’ve been writing for a while, especially if you’ve started using AI tools regularly. You stop asking whether AI was used at all, and instead start wondering how much it actually shaped what you’re reading. That shift is subtle, but once you notice it, you can’t really unsee it.
Automating JAMF Pro Email Notifications with SendGrid (Smart Group Driven Workflows)
Modern device management isn't just about enforcing policies—it's about communicating effectively with users at the right time. In JAMF Pro, Smart Groups give you powerful visibility into device state, but they don't natively solve the problem of proactive, automated user communication. Whether you're trying to prompt users to restart their machines, complete updates, or take action on compliance issues, bridging that gap requires a flexible and scalable notification system.
Leaving Flickr: Migrating 20,000+ Photos to Synology and Taking Back Control
There’s a certain kind of friction you start to notice when you’ve been using a service for a long time. Not enough to make you leave immediately, but enough to make you pause. Flickr had been that kind of service for me. It quietly held years of photos, uploads from old phones, albums I hadn’t looked at in ages, and a massive "Auto Upload" collection that had grown into something I didn’t fully understand anymore.
Exploring the Apple Business Manager API: A Hands-On Playground
If you’ve ever tried to talk directly to the **Apple Business Manager (ABM) API**, you already know the process can feel like deciphering a secret code. Between private keys, encrypted certificates, ES256 signatures, and OAuth2 flows... there’s a lot going on under the hood. This complexity is what makes direct communication with ABM so challenging, requiring a deep understanding of its intricacies to navigate successfully.
Updating Safari on macOS with Jamf Pro: Three Practical Strategies
Keeping Safari updated is one of the simplest ways to harden a macOS fleet. Apple ships security fixes for Safari frequently, and those patches often land before a full macOS point release. This means that by keeping Safari up-to-date, you can ensure your users have access to the latest security protections without having to wait for a major operating system update. If Safari is lagging behind, your users are browsing the web with a larger attack surface than necessary.
Hunting Down Jamf Profile Payloads with Python
If you've spent enough time living inside Jamf Pro, you eventually run into the same problem: someone set a configuration somewhere, sometime, and nobody remembers where. It might be something obscure – a certificate payload, a conditional SSO predicate, or that one security preference quietly misbehaving on three machines in accounting. And when you have dozens of configuration profiles, each with multiple payloads, nested keys, and XML-wrapped values, finding that setting can feel like forensic archaeology.
Keeping Jamf Security Cloud Current for Microsoft 365: Updated Routing Policies
When I first wrote about troubleshooting Standard Routing Policies in Jamf Security Cloud, the goal was simple: help admins keep Microsoft Teams and Microsoft 365 traffic flowing smoothly through Jamf Trust + App-Based VPN. This straightforward objective remains unchanged, as the complexities of network configurations can often lead to frustrating issues that hinder productivity.
Ensuring Jamf Trust VPN Stays Connected with Jamf Pro
Keeping your organization's VPN always connected is crucial—especially with Zero Trust Network Access (ZTNA) frameworks like **Jamf Trust**. One of the challenges with **Jamf Trust** is that it does *not* automatically open or reconnect on startup or login by default. However, with a combination of Jamf Pro policies, a custom script, and an extension attribute, you can ensure your users stay securely connected at all times, even when their devices are restarted or logged out. This setup helps maintain continuous access to network resources while adhering to the security standards...
Troubleshooting Standard Routing Policies in JAMF Security Cloud
As a fairly new administrator of JAMF Security Cloud, it was the ease of which its administration that admittedly drew me in. Quite an elegant solution for securing the various apps on business workstations with premade app-based VPN routing rules built right in, I was hooked. The concept is simple: turn on the policies, create your enrollment, and deploy – and you're done. This straightforward approach has made it easy to integrate into our existing workflow, allowing us to focus on more critical tasks.
Enrolling M1-M4 Devices into Automox with JAMF with secure tokens
Managing Secure Tokens on macOS has long been a challenge for administrators using JAMF and Automox. In my previous post, Managing the macOS Secure Token with JAMF Pro, I discussed a script-based approach to grant Secure Tokens to additional users. However, this method required administrators to manually pass usernames and passwords into the JAMF configuration—an approach that, while effective, was not ideal from a security or usability perspective. This manual process introduced unnecessary risks and added complexity to the overall management of Secure Tokens.