How to Run a Read-Only CIS Security Check on macOSSkip to article
TL;DR — I wanted to check a Mac against established security best<br>practices and found NIST’s open-source macOS Security Compliance Project<br>(mSCP). This article shows how to run a verified, read-only CIS Level 1<br>audit on macOS 14, 15, or 26:
zsh scan_cis.zsh --baseline cis_lvl1
The scan does not remediate settings; it writes the report and its temporary<br>dependencies under /tmp.
I wanted a simple answer to a simple question: how close is this Mac to a CIS<br>security baseline? I did not want a script that quietly changes settings<br>while trying to answer it.
The macOS Security Compliance Project (mSCP)<br>is NIST’s open-source toolkit for producing macOS security guidance and<br>compliance checks. It can generate a zsh compliance script for a chosen<br>baseline; its documented --check mode performs checks only, while --fix<br>applies remediation. That distinction matters.
For the first pass, I built macos-cis-scan:<br>a small wrapper that generates an mSCP audit and invokes it only as<br>--check. It never invokes --fix or --cfc. A failed rule is a prompt to<br>review a setting, not evidence that the scanner changed it.
Why use a wrapper?
mSCP is the authoritative component here, and its<br>quick guide<br>explains how to select a baseline and generate a compliance script. That is a<br>good route when you are building a managed compliance workflow.
For a person who needs one reproducible local audit, there is still some setup<br>to make explicit: obtain the source, generate the baseline, provide Python and<br>Ruby dependencies, run the generated script with the right privilege, and<br>retain the resulting report. The wrapper keeps that setup in one temporary<br>directory and records the versions and hashes used for the run.
It is deliberately not an installer. It does not create a launch agent, add a<br>profile, alter a CIS setting, or leave a Python environment behind.
What this release supports
At the time of writing, release<br>v0.1.0<br>supports the mSCP 2.0 rule library for macOS 14 Sonoma, 15 Sequoia, and 26<br>Tahoe. The script was tested on Apple Silicon with macOS 26; both Apple<br>Silicon and Intel portable-Python downloads are supported. It exits before<br>downloading dependencies on an unsupported macOS version.
macOS 13 Ventura and earlier are intentionally out of scope. They need a<br>separately reviewed workflow built around mSCP 1.0 rather than a forced run of<br>this script.
What you need before you start
For a full audit, the Mac needs:
macOS 14, 15, or 26;
an administrator account — sudo is used only immediately before the audit<br>reads privileged configuration;
the bundled zsh, curl, tar, and shasum commands;
git, plus the system ruby and gem commands;
outbound HTTPS access to GitHub, PyPI, and RubyGems; and
about 1 GB of free space on the volume backing /tmp.
You do not need to pre-install Python, pip, a virtual environment, Bundler,<br>or any Ruby gems. The script downloads a fixed portable CPython build, verifies<br>its SHA-256 before extracting it, and puts Python, gem state, caches, the mSCP<br>checkout, and the generated audit under a unique directory in /tmp.
The script is explicitly executed with zsh, so it does not matter whether a<br>person’s interactive shell is zsh, bash, or something else.
Download, verify, and run the audit
Download a named release rather than a copied snippet or a moving branch. You<br>can copy the following block as a whole: it creates a unique working directory<br>in /tmp, downloads and verifies the v0.1.0 release, runs the audit, and<br>then deletes only the downloaded release files. Nothing is piped into a shell.
Choose one baseline:
cis_lvl1 — the default and practical starting point for a personal or<br>standard work Mac.
cis_lvl2 — substantially stricter; pilot it before broad use.
set -e<br>work_dir="$(mktemp -d /tmp/macos-cis-scan-v0.1.0.XXXXXX)"<br>trap 'rm -rf "$work_dir"' EXIT<br>cd "$work_dir"
curl -fLO https://github.com/r4kh1m/macos-cis-scan/releases/download/v0.1.0/scan_cis.zsh<br>curl -fLO https://github.com/r4kh1m/macos-cis-scan/releases/download/v0.1.0/SHA256SUMS<br>curl -fLO https://github.com/r4kh1m/macos-cis-scan/releases/download/v0.1.0/SHA256SUMS.sig<br>curl -fLO https://github.com/r4kh1m/macos-cis-scan/releases/download/v0.1.0/r4kh1m-release-signing-key.pub
shasum -a 256 -c SHA256SUMS<br>printf 'r4kh1m-release namespaces="file" '<br>cat r4kh1m-release-signing-key.pub<br>} > allowed_signers<br>ssh-keygen -Y verify -f allowed_signers -I r4kh1m-release -n file -s SHA256SUMS.sig<br>The parentheses run this workflow in a separate shell, so set -e and the<br>cleanup trap do not change the user’s interactive shell. A failed download or<br>verification stops the workflow before the audit. The checksum check answers,<br>“are these the exact files listed by the release?” The signature check answers,<br>“was that manifest signed by the release key?” Before trusting a new key,<br>compare its fingerprint with the release notes. You can also inspect the<br>script directly — it is a plain zsh file —...