First hardening of the network layer
In the last post I gave quite some theory and an exercise: did you do it? Do you have your threat model? If not, go to the previous post and prepare your profile ASAP.
We’re about to get to the nitty-gritty details, here.
Previous posts on this series
- macOS Hardening: a new series
Introduction
Enough with theory, now it’s time to start hardening the system.
From here onwards, I will assume you are working on a fresh machine. To emulate this set of circumstances, I created a fresh VM. In general, I use either VirtualBuddy or UTM for virtualisation. I am going to use the former. I have installed the latest macOS version, which at the time of this writing is Tahoe 26.3.
We are going to install software and change settings. For this reason, I want to create a baseline, to observe what changes as we go.
Note that most of what we are going to do can also be done on an existing machine. I will often write “do something on your new machine” just because I am working on a fresh VM - but chances are that the concepts hold on an existing machine, mutatis mutandis.
Persistence
We start with the obsession of every hacker: Persistence. In this context, persistent software is a software that runs constantly on your machine, the typical example being startup items or processes. Then, let’s enumerate what we have on a clean machine:
desdinova@desdinovas-Virtual-Machine ~ % ls ~/Library/LaunchAgents
ls: /Users/desdinova/Library/LaunchAgents: No such file or directory
desdinova@desdinovas-Virtual-Machine ~ % ls /Library/LaunchAgents
desdinova@desdinovas-Virtual-Machine ~ % ls /Library/LaunchDaemons
desdinova@desdinovas-Virtual-Machine ~ % launchctl list | head -20
PID Status Label
1478 0 com.apple.trustd.agent
- 0 com.apple.MailServiceAgent
- 0 com.apple.mdworker.mail
- 0 com.apple.appkit.xpc.ColorSampler
818 0 com.apple.cfprefsd.xpc.agent
- 0 com.apple.coreimportd
- 0 com.apple.CoreDevice.remotepairingd
- 0 com.apple.TrustedPeersHelper
- 0 com.apple.cvmsCompAgent3600_arm64_1
- 0 com.apple.SafariHistoryServiceAgent
- 0 com.apple.progressd
- 0 com.apple.enhancedloggingd
1285 0 com.apple.cloudphotod
1001 0 com.apple.MENotificationService
1233 0 com.apple.Finder
941 0 com.apple.homed
969 0 com.apple.dataaccess.dataaccessd
- 0 com.apple.quicklook
- 0 com.apple.parentalcontrols.check
The directories we checked (~/Library/LaunchAgents, /Library/LaunchAgents, /Library/LaunchDaemons) are where macOS looks for software that should start automatically. The launchctl list command shows what’s actually running right now.
On a clean machine, these directories are empty or don’t exist, and launchctl list shows only com.apple.* processes — all Apple’s own. Anything else is something you added, or something that was added for you.
KnockKnock
KnockKnock is a program developed by Patrick Wardle that checks for persistent software. The description from the website is:
Malware often installs itself persistently, to ensure it is automatically (re)executed each time a computer is restarted. KnockKnock uncovers persistently installed software in order to generically reveal such malware.
The question that arises here is: why do you trust this software? It is a totally fair question, because as a matter of fact we are giving this software the role of controller.
As Juvenal teaches us: Quis custodiet ipsos custodes? - who watches the watchmen? This could become an infinite regress, so here are some principles:
- if it’s developed by you and you’re not at war with yourself, you can trust the software;
- if you can audit it, and it doesn’t look suspicious (and once again, you’re not a Jekyll-Hyde sysadmin), trust the software;
- if it’s a well-known component, adopted by people in the community, chances are that you can trust the software;
- if your only reason to trust software is a gut feeling, don’t. You’re not lucky — nobody is. When it comes to security, a healthy dose of paranoia is warranted.
Installation
Installing KnockKnock is extremely straightforward:
- Point your browser to https://objective-see.org/products/knockknock.html
- Click on Download
- Open a finder window on the Download folder, and drag
KnockKnockto/Applications.
The configuration is simple:
- you want the software to start at login - this will show you what runs at startup
- grant KnockKnock full disk access (see below)
- I suggest enabling VirusTotal integration.
- In order to do so, you need to have a VirusTotal API key, which is free
- Point your browser to https://docs.virustotal.com/docs/please-give-me-an-api-key and follow the instructions
Application:

Settings:

The setup documentation in KnockKnock download page is exhaustive - for the sake of brevity, I steer you to it for the initial setup.
Run a scan. The first thing you install immediately shows up. KnockKnock reports itself as a login item (because we configured it that way) and as a background task. No surprises — this is exactly what we want to see.
Below, KnockKnock detecting itself


But I am more of a terminal guy, so I launch the commands I executed before:
desdinova@desdinovas-Virtual-Machine ~ % ls ~/Library/LaunchAgents
ls: /Users/desdinova/Library/LaunchAgents: No such file or directory
desdinova@desdinovas-Virtual-Machine ~ % ls /Library/LaunchDaemons
desdinova@desdinovas-Virtual-Machine ~ % ls /Library/LaunchAgents
desdinova@desdinovas-Virtual-Machine ~ % launchctl list | head -20
PID Status Label
1478 0 com.apple.trustd.agent
- 0 com.apple.MailServiceAgent
- 0 com.apple.mdworker.mail
- 0 com.apple.appkit.xpc.ColorSampler
818 0 com.apple.cfprefsd.xpc.agent
- 0 com.apple.coreimportd
- 0 com.apple.CoreDevice.remotepairingd
- 0 com.apple.TrustedPeersHelper
- 0 com.apple.cvmsCompAgent3600_arm64_1
- 0 com.apple.SafariHistoryServiceAgent
- 0 com.apple.progressd
- 0 com.apple.enhancedloggingd
1285 0 com.apple.cloudphotod
1001 0 com.apple.MENotificationService
1233 0 com.apple.Finder
941 0 com.apple.homed
969 0 com.apple.dataaccess.dataaccessd
- 0 com.apple.quicklook
- 0 com.apple.parentalcontrols.check
Interestingly, our manual commands don’t show KnockKnock at all — the directories are still empty, launchctl list shows only Apple processes. Yet KnockKnock found itself.
This is because modern macOS has multiple persistence mechanisms beyond LaunchAgents and LaunchDaemons. Login Items and Background Tasks use different APIs entirely. The manual commands give you a partial view; KnockKnock gives you the full picture.
In macOS Ventura and later versions, the Background Task Manager can be interrogated with:
desdinova@desdinovas-Virtual-Machine ~ % sudo sfltool dumpbtm
Password:
========================
Records for UID -2 : FFFFEEEE-DDDD-CCCC-BBBB-AAAAFFFFFFFE
========================
ServiceManagement migrated: true
LaunchServices registered: false
Items:
... SNIP ...
========================
Records for UID 501 : D3853C4B-E40A-4909-B854-6100D33520CE
========================
ServiceManagement migrated: true
LaunchServices registered: true
Items:
#1:
UUID: CD1245CF-C031-4940-B31B-E0C7A007368F
Name: KnockKnock
Developer Name: (null)
Team Identifier: VBG97UB4TA
Type: app (0x2)
Flags: [ ] (0)
Disposition: [enabled, allowed, notified] (0xb)
Identifier: 2.com.objective-see.KnockKnock
URL: file:///Applications/KnockKnock.app/
Generation: 1
Bundle Identifier: com.objective-see.KnockKnock
There it is: com.objective-see.KnockKnock, disposition enabled, allowed, notified. The system knows about it — we just needed to ask the right way
Package management
Dragging and dropping every app into Applications would make this guide tedious. Enter Homebrew:
desdinova@desdinovas-Virtual-Machine ~ % /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
==> Checking for `sudo` access (which may request your password)...
Password:
==> This script will install:
/opt/homebrew/bin/brew
/opt/homebrew/share/doc/homebrew
/opt/homebrew/share/man/man1/brew.1
/opt/homebrew/share/zsh/site-functions/_brew
/opt/homebrew/etc/bash_completion.d/brew
/opt/homebrew
/etc/paths.d/homebrew
...
SNIP
...
Software Update Tool
Finding available software
Downloading Command Line Tools for Xcode 26.2
Downloaded Command Line Tools for Xcode 26.2
Installing Command Line Tools for Xcode 26.2
Done with Command Line Tools for Xcode 26.2
Done.
...
SNIP
...
==> Next steps:
- Run these commands in your terminal to add Homebrew to your PATH:
echo >> /Users/desdinova/.zprofile
echo 'eval "$(/opt/homebrew/bin/brew shellenv zsh)"' >> /Users/desdinova/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv zsh)"
- Run brew help to get started
- Further documentation:
https://docs.brew.sh
desdinova@desdinovas-Virtual-Machine ~ %
Observe that the Command Line Tools for Xcode 26.2 are downloaded and installed. This will come in handy later.
Launch the last three commands specified in the installation script, and you’re ready to go.
After installing Homebrew, a new KnockKnock scan reveals one change: .zprofile. Homebrew adds itself to your shell’s PATH there. No launch agents, no daemons — just a line in a config file that runs every time you open a terminal.
This is another persistence mechanism the manual commands miss entirely. Profile files are a classic and often overlooked way for software (legitimate or not) to ensure it runs.
I encourage you to run the manual commands again, and launch a new KnockKnock scan. You’ll confirm what I just described — and more importantly, you’ll build the habit of checking before and after every installation.
Groundwork done. Time to harden.
Hardening strategy
A note: it’s impossible to focus on a single domain (say, “let’s harden the network”) without touching others. We’ll install software and change configurations at the same time, jumping between domains as needed. This requires some mental organisation from you, reader. But we can do it :)
To draft a good strategy, we need to remember the threats we identified in the Threat Modelling phase. For convenience, let’s rewrite the list here:
- Software
- information loss due to technical issues
- information loss due to myself
- information theft
- malware
- Public image
- customer’s data exfiltration
- my websites being hacked
- Laptop
- Accidents
- Laptop Loss
- Laptop Stolen
- My personal data
- My preferences being gathered without my consent
- Tracking
We can prune the Laptop entry from the list — the best mitigation there is insurance on the hardware. Unfortunately, I haven’t been able to find an insurance company that covers the contents of a laptop. I doubt I ever will.
To configure our machine we will use several websites, so the first “enemies” we will encounter are… cookies, trackers, and all those indecent phukeries.
This, incidentally, means that the first component that we need to configure is the network. My strategy is:
- Armour the DNS
- Install an Application Firewall
- Block cookies
Armouring the DNS: DNS is the very first thing queried when a browser or any other application makes a request. Before HTTP, there’s DNS. If we block trackers, adware, and malware at this level, we cripple harmful components before they start. Benefits:
- Preventive block, before traffic is initiated
- Works for all apps
- No software to install
- Query logging
Application firewall: what if a component queries an IP address directly, rather than a FQDN? What if it connects to a domain that’s not blacklisted? An application firewall shows all outbound connection attempts, asking for permission to establish that connection. Benefits:
- Visibility on who connects where
- Granular, per-app control
- Spots “phone home” and telemetry
- Helps write network firewall rules
- If DNS blocks known harmful domains, an application firewall blocks unknown connections
HTTP filtering: in line with our risk profile, we also need to work at the application level — inspecting HTTP requests themselves:
- Header spoofing (User-Agent, Referer)
- Blocking tracking pixels and inline scripts
This is layered defence in action: all three must fail for the system to fail.
A cautionary note
In this series I am teaching you not only to harden your machine, but also how to protect your identity.
We will harden systems and services, within the (strict) scope of what is under your control. Shortly we’ll discuss how to harden the access to your email; obviously we cannot harden the mail server if it’s under someone else’s control.
This leads to a delicate topic: federated logins. I know many people who claim federated login has advantages, and many of my customers push their customers to use them. Understandable. But from our perspective, this practice is at best doubtful. For instance, our next step will be hardening DNS queries using an external service (NextDNS). Suppose you opt for Google login. If you go this route, you’ve just linked:
- the service that manages all your DNS queries
- your Google identity
which contradicts our hardening principles.
In short: “Sign in with Google/Facebook/Apple/GitHub”: are convenient, but they link your identity across services. If you’re hardening for privacy, consider using a dedicated email and password instead. Apple’s “Hide My Email” is a reasonable middle ground.
Already signed up with Google or Facebook? The link is made — that data point exists. Switching to a dedicated email now limits future tracking, but doesn’t erase the past. If the service allows changing your login method, do it. If not, consider whether it’s worth creating a fresh account. For new services, start clean.
DNS Hardening
NextDNS
NextDNS is a DNS resolver that filters malicious domains, trackers, and ads before they even reach your machine. Think of it as a bouncer at the door — if a domain is on the blacklist, the connection never happens.
Why NextDNS:
- Blocks at DNS level — works for all apps, not just browsers
- No software required on the machine (though an app exists)
- Encrypted queries (DNS-over-HTTPS) — your ISP can’t see what you’re resolving
- Logging with Swiss storage — useful for auditing, privacy-respecting jurisdiction
Setup:
- Create an account at nextdns.io — use a dedicated email, not your main one
- Create a new Configuration (I called mine “Cyber Implants Profile”)

This is what you would see after creating a fresh account. As you may notice, the service detects the active DNS.
Security tab — enable everything:
- Threat Intelligence Feeds
- Google Safe Browsing
- Cryptojacking Protection
- DNS Rebinding Protection
- IDN Homograph Attacks Protection
- Typosquatting Protection
- DGA Protection
- Block Newly Registered Domains
- Block Dynamic DNS Hostnames
- Block Parked Domains
Privacy tab — add blocklists:
- NextDNS Ads & Trackers Blocklist
- OISD
- AdGuard Tracking Protection
Under “Native Tracking Protection”, enable Apple and Windows.

Settings tab:
- Enable Logs
- Storage location: Switzerland
- Retention: your choice (I use 3 months)
Now copy the account ID, download and install the NextDNS utility from the Apple Store, and write your ID in the field:

Refresh the NextDNS Setup page. You should see “All good! This device is using NextDNS with this profile.”

Test it:
nslookup ads.google.com
If NextDNS is working, you’ll see blockpage.nextdns.io instead of Google’s IP.
Application Firewalls
NextDNS blocks known bad domains. But what about:
- Apps connecting to IP addresses directly?
- Domains not on any blocklist?
- Legitimate domains used for telemetry?
An application firewall intercepts every outbound connection and asks: should this app be allowed to connect here?
LuLu vs Little Snitch
I use Little Snitch on my daily driver — it’s polished, feature-rich, and worth the ~€69. But for this guide, we’ll use LuLu:
- Free and open source
- Same author as KnockKnock (Patrick Wardle, Objective-See)
- Does the core job well
If you want more features later, Little Snitch is the upgrade path.
Installation
Installing LuLu is straightforward, and well described on the Objective-See page. After you enable the System Extension, the installation is complete.
On first launch, LuLu will ask about many Apple processes. This is normal — your system is chatty. Allow the legitimate ones, and the noise will settle.
The default settings and modes cover the basic needs of most users:



Testing
To test it, install wget:
brew install wget

If LuLu is configured properly, you’ll see an alert:

In this case, curl (used by Homebrew) needs to reach GitHub. Allow it — this is legitimate. If you see something unexpected trying to connect, that’s when LuLu earns its keep.
LuLu Rule Edit
You can also write your own rules manually.

My philosophy: block everything unless explicitly needed. When in doubt, block. If something breaks, you’ll know immediately — and you’ll learn what your system actually needs to function. This is more valuable than any predefined ruleset I could give you.
HTTP Filtering
DNS blocks domains. The application firewall blocks connections. But what about the content of HTTP requests?
Privoxy is a filtering proxy that sits between your browser and the internet. It can:
- Spoof headers (User-Agent, Referer) to reduce fingerprinting
- Block tracking pixels and analytics scripts
- Filter ad-related content that slipped through DNS
Installation
brew install privoxy
Start the service:
brew services start privoxy
Privoxy runs on 127.0.0.1:8118 by default.
System Configuration
To route your traffic through Privoxy:
- Open System Settings → Network → Wi-Fi → Details → Proxies
- Enable Web Proxy (HTTP) and Secure Web Proxy (HTTPS)
- Set both to
127.0.0.1port8118
Now all HTTP/HTTPS traffic passes through Privoxy.
The Problem: Toggle
Sometimes Privoxy breaks things. A site won’t load, a login fails, something times out. You need to quickly disable it, fix your problem, and re-enable it. And it’s not that easy anyway. For instance, the web interface of my MikroTik cannot coexist with Privoxy. Thanks god for the Terminal interface.
Going through System Settings every time is tedious. Enter SwiftBar.
SwiftBar
SwiftBar lets you run scripts from the menu bar. We’ll use it to toggle Privoxy on and off with one click.
brew install swiftbar
Create a file called privoxy.1m.sh in your SwiftBar plugins folder:
#!/bin/bash
# Privoxy
# true
if pgrep -x privoxy > /dev/null; then
echo "🛡️"
echo "---"
echo "Privoxy running | color=green"
echo "Stop | shell=/opt/homebrew/bin/brew param1=services param2=stop param3=privoxy terminal=false refresh=true"
else
echo "🛡️"
echo "---"
echo "Privoxy stopped | color=red"
echo "Start | shell=/opt/homebrew/bin/brew param1=services param2=start param3=privoxy terminal=false refresh=true"
fi
Make it executable:
chmod +x privoxy.1m.sh
Now you have a shield icon in your menu bar. Green means running, red means stopped. One click to toggle.
Testing
With Privoxy enabled, visit a site that shows your headers (e.g., httpbin.org/headers). You should see Privoxy’s modifications.
With Privoxy disabled, the same site shows your real headers.
Conclusion
The structure we are creating is consistent with the principles that we gave in the previous post. We need to build layers of defence.
In the previous post, we assessed some threats and declared the objectives we want to tackle.
Here we started talking about:
- package management
- persistence management
- network defence.
In detail, we have built three layers of network defence:
- DNS (NextDNS) — blocks known bad domains before connections start
- Application Firewall (LuLu) — catches what DNS misses, gives per-app control
- HTTP Filtering (Privoxy) — inspects and modifies request content
All three must fail for tracking or malware to succeed. That’s layered defence.
This is just the network layer. In the next post, we’ll tackle browser compartmentalisation — because even with all this, your browser is still the biggest attack surface.
Stay paranoid, but have fun!
References
- https://objective-see.org/products/knockknock.html
- https://objective-see.org/products/lulu.html
- https://nextdns.io
- https://www.privoxy.org
-
https://github.com/swiftbar/SwiftBar
Wanna interact?
Then come visit me on mastodon: https://infosec.exchange/@gbiondo