Santa on your Macs, the uninvited binary stays at the door
Install Santa on your Macs, map the fleet in Monitor mode, switch to Lockdown, write your allowlist by TeamID, and feed every block into Wazuh.
In the previous episode, you put Rspamd in front of your inbox. Every incoming message is scored, phishing raises an alert, and the verdict goes up into Wazuh. Three bricks standing, the server, the network, the mail. Your SIEM sees, it blocks, it filters.
And one door remains. The last one, the one none of the first three guards.
An employee downloads a file. A fake update for a piece of software he knows, a utility found on a forum, an attachment that got through the mail filter because it was clean on the day it came in. He double-clicks. The binary starts. At that precise second, neither CrowdSec nor Rspamd has any say, execution happens on the Mac, locally, far from your servers.
That’s the blind spot that’s left. Your setup watches everything that comes in, and lets anything run once it’s in.
Today, we close that door. We install Santa, an execution control system for macOS. Its principle is brutally simple, you draw up the list of applications allowed to run, and everything else is refused. Then we wire its decisions into Wazuh, so that every blocked binary shows up in your dashboard, next to your mail rejections and your network blocks.
Before you begin
Three things to keep in mind before the first command.
What you need. One or more Macs on a recent version of macOS, administrator access on them, and the Wazuh agent from episode 3 already installed on those Macs, for the integration part at the end of the tutorial. Santa is open source, maintained by North Pole Security, the company founded by its original creators, the ones who wrote it at Google in 2014. Apache 2.0 license, zero paid version for what we’re doing here.
What changes. By the end, an unknown binary no longer runs on your Macs. Not “runs and raises an alert”, does not run. You go from a machine that launches whatever is put in front of it to a machine that only launches what you approved, and every blocked attempt goes up into your SIEM.
What doesn’t change. Santa controls the execution of binaries. It doesn’t read the contents of a Python or shell script, we’ll come back to that, it’s its most important limitation and I’m not going to hide it from you. It doesn’t replace your antivirus, it doesn’t repair an account whose password has leaked. And it doesn’t tune itself, the real expense isn’t RAM, it’s the list you’re going to have to keep up to date.
Santa forbids nothing, it only permits
The mental reversal that makes the whole thing work, and that separates Santa from an antivirus.
An antivirus works from a blacklist. It knows thousands of threats, it looks for them, it blocks them, and it lets through anything it doesn’t recognize as bad. The problem is structural, a threat it doesn’t know yet is a threat it authorizes. It chases evil, it’s always one step behind.
Santa does the opposite. It works from an allowlist. It doesn’t ask whether a binary is bad, it asks whether it’s authorized. What isn’t on the list doesn’t run, whether it’s malicious, unknown, or perfectly legitimate but forgotten. You stop chasing threats, you define once what has the right to exist on your machines.
That switch is set with a single parameter, the client mode, and it has three positions.
Monitor. Everything runs, but everything is logged. Santa watches, notes, and blocks nothing. It’s the observation mode, the one you always start with.
Lockdown. The lock. What isn’t explicitly authorized is refused. That’s the target position, the one that closes the door.
Standalone. In between. An unknown binary triggers a window asking the user to authorize it himself, Touch ID and all. Useful on an isolated machine, but it hands the decision to the employee, exactly what a company allowlist is trying not to do.
The progression is mechanical. You start in Monitor to map what actually runs across the fleet, you build the allowlist from those observations, and you switch to Lockdown once you’re sure you won’t block anyone useful. You never lock down blind.
That leaves the question of how Santa recognizes an application. It has five ways of identifying one, from the most precise to the broadest.
- The CDHash, the fingerprint of an exact version of a binary. Changes on the slightest update.
- The SHA-256 hash of the binary itself. Just as fragile, it breaks on every update.
- The SigningID, the signing identifier of a specific application from a given vendor.
- The signing certificate, broader, it covers everything a certificate signs.
- The TeamID, the Apple Developer team identifier. The broadest on the vendor side, it authorizes all applications signed by that developer.
Santa evaluates them in that order, from the most specific to the most general, and the first matching rule wins. For a small business, the right level is almost always on the last two. The TeamID says “I trust this vendor”, a single rule that survives all of its updates. The SigningID narrows things down when you want to authorize a specific application from a vendor without writing it a blank check. The hashes, you leave for the special cases, otherwise you spend your time rewriting rules that the next update will break.
Step 1, install Santa and start in Monitor
On each Mac, you fetch the signed installation package from North Pole Security’s official releases, and you install it.
sudo installer -pkg santa-*.pkg -target /
Santa runs as a system extension, of the Endpoint Security kind, the same API family as the serious security tools on macOS. Concretely, two authorizations will be asked of you on first installation. macOS asks you to approve the system extension, in System Settings, Privacy & Security. And Santa has to be granted Full Disk Access, so it sees everything that runs.
You recognize this Full Disk Access, it’s the Wazuh agent’s from episode 3, the same TCC constraint specific to macOS. On an MDM-managed fleet, those two approvals are pushed by configuration profile, once and for all, on every machine. Without MDM, you tick them by hand, machine by machine. That’s the first place where the absence of MDM is paid for in manual handling, may as well know it straight away.
Once installed, Santa starts in Monitor by default. That’s exactly what we want. Check its state.
santactl status
You should read a Monitor mode and an active extension. From now on, every execution on that Mac is noted in its log, without anything being blocked. Let it run. A normal working day, a week if you can, long enough for your employees to launch everything they usually launch. You’re not installing a wall today, you’re taking photographs of who walks through the door.
Step 2, map your fleet and write your allowlist
Santa writes its decisions to /var/db/santa/santa.log, one line per execution. Go and read them, that’s your raw material.
tail -f /var/db/santa/santa.log
Each line gives you the action, the decision, the reason, the binary’s path, its vendor. In Monitor, everything is decision=ALLOW, but the reason teaches you why Santa would have let it through, or would have blocked it in Lockdown. A reason=UNKNOWN is a binary that nothing authorizes, exactly what would fall over on switchover day.
To turn an observation into a rule, you need the application’s identity. Santa gives it to you.
santactl fileinfo /Applications/SomeApp.app
It shows you the signature, the TeamID, the SigningID, the fingerprints, and the rule that would apply today. That’s where you decide on the right level. A vendor you trust across its whole catalog, you authorize by its TeamID.
sudo santactl rule --allow --teamid --identifier <TEAMID> --comment "in-house vendor"
A specific application, you take by its SigningID.
sudo santactl rule --allow --signingid --identifier <SIGNINGID>
You repeat for every legitimate vendor and every legitimate application that Monitor saw go by. Your list is built from reality, not from a theoretical catalog. That’s the groundwork of this episode, and there’s no shortcut, a good allowlist is one that fits your actual usage.
For one or two Macs, those rules live locally on each machine and that’s enough. For a fleet, you’re not going to type the same commands on ten workstations. Santa knows how to synchronize from a central server that distributes the same rules to the whole fleet. The sovereign option is Moroz, a small server that serves your rules from plain files, which you host yourself. You put it on your infrastructure and you keep it Tailnet-only, the same doctrine as your Wazuh manager since episode 1, nothing needless on the open internet. North Pole Security also offers its own turnkey synchronization service, richer but hosted by a third party, your call between convenience and full sovereignty.
Step 3, switch to Lockdown
Your allowlist is ready, Monitor has nothing new left to teach you. Time to close up.
And this is where the trap hides, the one that costs everyone half an hour. You cannot switch the mode with defaults write. You can write it, defaults will tell you it’s done, defaults read will confirm it, and santad will quietly carry on running in Monitor. Reboot included.
That’s not a bug. It’s a security decision. Santa only reads its mode from managed preferences, the ones a configuration profile drops, not from the ordinary preferences a user could modify. The reason is crystal clear, if a mere defaults write were enough to get out of Lockdown, the first piece of malware to come along would use it to free itself. The lock only opens with a key the current user doesn’t have.
That key is a configuration profile, a .mobileconfig file. On a managed fleet, your MDM pushes it to every machine. Without MDM, you write the profile and install it by hand, via System Settings, Profiles, Install, administrator password or Touch ID required. The profile contains the mode and the logging configuration.
<key>PayloadType</key>
<string>com.northpolesec.santa</string>
<key>ClientMode</key>
<integer>2</integer>
<key>EventLogType</key>
<string>file</string>
<key>EventLogPath</key>
<string>/var/db/santa/santa.log</string>
ClientMode at 2 is Lockdown. The EventLogType at file and the EventLogPath are what guarantee that Santa keeps writing its log to the place Wazuh will go and read at the next step. Once the profile is installed, santad takes it into account immediately.
santactl status
The mode now shows Lockdown. The door is closed. Test it right away, download some small utility, a binary you haven’t allowlisted, and launch it.
./some-random-binary
macOS tells you the execution was blocked by Santa. That’s the expected behavior, and it’s the first time anything has really stood in the way of a double-click on your fleet. That refused execution has also just landed in /var/db/santa/santa.log, as decision=DENY. All that’s left is to feed it upwards.
Step 4, feed Santa into Wazuh
Santa blocks, on each Mac, in its own corner. Your SIEM doesn’t know it yet. We wire them together, and the series’ loop closes on the workstation.
Nothing to enable on the Santa side, it’s already writing its log. The wiring happens on two machines, and it’s the same division of roles as in episode 5. The Wazuh agent, on each Mac, reads the file and ships the raw lines. The manager, on your VPS from episode 2, decodes and decides what to do with them. One machine collects, the other understands.
On the Mac side, you tell the agent to read Santa’s log. And rather than editing each machine one by one, you reuse the groups from episode 3. The block goes in the shared configuration of the macos group, on the manager, in /var/ossec/etc/shared/macos/agent.conf, and the manager pushes it to all your Macs in one go.
<localfile>
<location>/var/db/santa/santa.log</location>
<log_format>syslog</log_format>
</localfile>
You recognize the <localfile> from episode 3, the mechanism by which Wazuh swallows any log source. The agent runs as root, it reads /var/db/santa/ without difficulty.
On the manager side, now, and on it alone. A decoder extracts the useful fields from the line, in /var/ossec/etc/decoders/local_decoder.xml.
<decoder name="santa">
<prematch>santad: action=EXEC</prematch>
</decoder>
<decoder name="santa-decision">
<parent>santa</parent>
<regex type="pcre2">decision=(\w+)\|reason=(\w+).*?\|path=(.+)$</regex>
<order>santa_decision, santa_reason, santa_path</order>
</decoder>
Then rules that turn those fields into alerts, in /var/ossec/etc/rules/local_rules.xml, still on the manager.
<group name="santa,">
<rule id="100500" level="0">
<decoded_as>santa</decoded_as>
<description>Santa, execution decision.</description>
</rule>
<rule id="100501" level="10">
<if_sid>100500</if_sid>
<field name="santa_decision" type="pcre2">^DENY$</field>
<description>Santa, execution blocked, binary $(santa_path).</description>
</rule>
<rule id="100502" level="3">
<if_sid>100500</if_sid>
<field name="santa_decision" type="pcre2">^ALLOW$</field>
<description>Santa, execution allowed, binary $(santa_path).</description>
</rule>
</group>
The parent rule is at level 0, it decodes without alerting. The block goes to level 10, that’s the one that interests you, a binary refused on a workstation is a signal you want to see. The authorization stays at level 3, a discreet trace, otherwise your dashboard would drown under the thousands of legitimate executions of the day, especially while you’re still in Monitor where everything is ALLOW. The ^DENY$ anchoring and not DENY, the same precaution as in episode 5, so it only matches the exact decision.
Don’t take this decoder on faith, test it. The tool lives on the manager, not on the agent.
sudo /var/ossec/bin/wazuh-logtest
You paste a real DENY line from your santa.log, and it shows you what it made of it, phase by phase, decoder recognized, fields extracted, rule triggered. If santa_decision and santa_path don’t come out, it’s the extraction pattern that needs adjusting, and you’ll know in thirty seconds. A word of honesty on this point, Santa’s lines start with a timestamp in square brackets that doesn’t have the shape of a classic syslog log. The pre-decoding and the pattern may need adjusting depending on your Wazuh version and the exact format of your lines. That’s precisely what wazuh-logtest confirms on your own machine, before you count on the alert.
Once the test is conclusive, restart the manager so it loads your decoder and your rules.
sudo systemctl restart wazuh-manager
An employee launches an unknown binary, Santa refuses it, writes it to its log, the Wazuh agent reads it and ships it over the Tailnet, the manager decodes it and raises a level-10 alert, it shows up in your dashboard next to your CrowdSec blocks and your Rspamd rejections. The last door is closed, and you watch it close.
If it doesn’t work
Problem, Santa is installed but logs nothing
Probable cause, the system extension wasn’t approved, or Full Disk Access is missing. Without them, santad doesn’t see executions.
Solution, in System Settings, Privacy & Security, check that the Santa extension is authorized and that Full Disk Access is granted to it. santactl status should report the extension as active.
Problem, the mode stays on Monitor no matter what
Probable cause, almost certainly, you tried the switch with defaults write. Santa ignores it, by design.
Solution, go through a .mobileconfig configuration profile, pushed by MDM or installed by hand in System Settings, Profiles. That’s the only channel Santa listens to for its mode.
Problem, you added rules but santactl status shows Static Rules: 0
Probable cause, you’re reading the wrong counter. santactl status separates rules by type, and Static Rules only counts those from a static configuration file, not the ones you add on the command line.
Solution, a rule set by TeamID counts in TeamID Rules, a SigningID rule in SigningID Rules. Look at the line for the type you actually created, your rules are there.
Problem, nothing goes up into Wazuh
Probable cause, three suspects, to sort out before touching anything. Either the file isn’t growing on the Mac side, or the agent isn’t reading it, or the manager doesn’t know how to decode it. The great classic, decoder and rules edited on the Mac, where they’re useless, instead of on the manager.
Solution, is the file growing? If it’s growing, go to the manager, run a line through wazuh-logtest and see at which phase it breaks. Check that local_decoder.xml and local_rules.xml are indeed on that machine, and that you restarted wazuh-manager after writing them.
What it cost you
The license, nothing. Santa is open source under Apache 2.0, no paid version, no enterprise tier, no per-seat quota.
The machine, nothing either. Santa runs on the Mac you already have. The synchronization server, if you set one up, is one more lightweight service on your existing infrastructure.
Your time, a little for the installation, a lot more for the calibration. And that’s the only real spending line, the one no SaaS will spare you.
Be clear-eyed about what Santa doesn’t do, because those blind spots are real and actionable.
It doesn’t see scripts. Santa controls executed binaries. A Python script, a shell script, a macro, are interpreted by a binary, python3, bash, osascript, that you have probably authorized. The script then slips under the radar, it’s the interpreter that gets judged, not the code it executes. That’s the major technical limitation, and it opens up a whole related project, hardening the interpreters themselves.
The allowlist is alive. Every new legitimate tool, every new vendor, is a rule to add. The TeamID limits the damage, one rule per vendor that survives its updates, but you don’t set this list once and leave it for ten years.
Switchover day hurts. Everything you forgot to allowlist is blocked outright, in the middle of a working day. A fleet’s first DENY is often an old dormant backup agent or a tool nobody remembered was installed. Hence the time spent in Monitor, and a fast allowlisting procedure kept on hand for the first few days.
Without MDM, it’s more manual. Extension approval, Full Disk Access, mode profile, everything is done machine by machine. Manageable at three Macs, painful at thirty.
None of these limits is a reason to give up. It’s the price of a control that stays entirely with you, no application submitted to a third party’s cloud to find out whether it’s allowed to run, no trust catalog rented from an American vendor. You decide, the decision applies locally, and it goes up into your SIEM.
For the impatient
What this article does. We install Santa on the Macs, start in Monitor mode to map what runs, write an allowlist by TeamID and SigningID, switch to Lockdown via a configuration profile, then feed every decision into Wazuh via a homemade decoder and rules. The workstation stops being the SIEM’s blind spot.
Concretely, the commands.
- Install Santa on each Mac:
sudo installer -pkg santa-*.pkg -target /, then approve the system extension and grant Full Disk Access. - Check the default mode:
santactl status, you should readMonitor. - Observe:
tail -f /var/db/santa/santa.log, let it run for several days. - Identify an app:
santactl fileinfo /Applications/SomeApp.app. - Authorize by vendor:
sudo santactl rule --allow --teamid --identifier <TEAMID>, or by app:--signingid --identifier <SIGNINGID>. - For a fleet, centralize the rules with a self-hosted synchronization server (Moroz), kept Tailnet-only.
- Lock down:
.mobileconfigprofile withClientModeat2,EventLogTypeatfile,EventLogPathon/var/db/santa/santa.log, pushed by MDM or installed in System Settings, Profiles.defaults writeis ignored. - Integrate into Wazuh, on two machines:
<localfile>on/var/db/santa/santa.logon the agent side (via the shared config of themacosgroup), decoder and rules on the manager side (block at level 10), validation with/var/ossec/bin/wazuh-logteston the manager, then a restart ofwazuh-manager.
In summary
You started from a setup that saw your servers, blocked your network and filtered your mail, but let any binary run once it had landed on a Mac.
You installed Santa on your workstations, and understood that it doesn’t work from a list of threats but from a list of the permitted, anything that isn’t invited stays outside. You mapped in Monitor what actually runs, then wrote your allowlist by TeamID and SigningID, the right level between precision and maintainability.
You locked it down in Lockdown by configuration profile, the only channel Santa listens to for its mode. And you wired all of it into Wazuh, decoder and rules, so that a refused binary raises a level-10 alert in the same dashboard as the rest.
The whole thing stays with you. No application goes off to be judged in someone else’s cloud, no third party decides in your place what has the right to run on your machines.
Your setup is complete. Four layers that see and that block, from the server to the workstation. What’s left is making it livable day to day. A SIEM that wakes you ten times a night over trifles, you end up ignoring, and a SIEM that gets ignored is useless. In episode 7, the last one, we calibrate the alerting so you’re only disturbed when it matters, and we do the math, the honest total cost of what you’ve just built, against the bill for an equivalent SaaS.
Series recap
Seven episodes to build your sovereign security plumbing, brick by brick.
- Episode 0, the bedrock of the series. Why an SME needs a sovereign SIEM, NIS2, GDPR, and the cost of doing nothing.
- Episode 1, the foundation. The hardened VPS beyond the CLOUD Act.
- Episode 2, Wazuh all-in-one. Manager, indexer and dashboard on a single node.
- Episode 3, agents everywhere. Deployment across your servers, Macs and Windows.
- Episode 4, community network defense. Blocking known attackers before they arrive.
- Episode 5, the mail filter. Anti-spam and anti-phishing in front of your mail server.
- Episode 6, execution control on the Macs. Deciding which applications are allowed to start. You’ve just read it.
- Episode 7, alerting and total cost. Calibrated notifications and an honest financial reckoning against SaaS.
Technical terms? Check the glossary.