Part of the Munki and plist Files: How One Folder of Text Serves Software for Mac Clients. Start there for the wide view, then come back here for the close-up.
Munki repo security comes down to four locks, and the project ships with only one of them turned. A Munki repository is a folder of plain text plists on a web server, which means every Mac in your fleet takes its installation orders from files anyone with the URL can read and anyone with the share can rewrite. Each lock is a documented setting or a server directive, not a product you have to buy. Here is the map.
| Lock | What it stops | Where it lives | Default |
|---|---|---|---|
| The wire | Reading or rewriting manifests in transit |
HTTPS on the server, SoftwareRepoCACertificate,
FollowHTTPRedirects
|
Plain HTTP works; redirects are not followed |
| The client | Any device with the URL pulling licensed software |
AdditionalHttpHeaders (basic auth) or
UseClientCertificate
|
Open to anyone who can reach the server |
| The payload | A swapped or corrupted installer |
installer_item_hash plus
PackageVerificationMode
|
hash: checked only when a hash exists |
| The pen | Anyone rewriting a manifest or catalog | File share permissions, Git over SSH, repo plugins | Whatever your file server allows |
The Munki Trust Map
The rows are not interchangeable. HTTPS keeps the wire honest but says nothing about who is on the other end of it. Client authentication decides who may read the repo but nothing about whether what they read was tampered with at rest. Package hashes prove the installer matches the pkginfo but the pkginfo itself carries no hash, so a rewritten catalog sails straight through. That gap is why the fourth lock is the only one Munki cannot enforce for you. It is a property of your file server, your Git host, or your repo plugin.
Munki's own documentation draws the line cleanly: HTTPS "only secures the contents of the data on the wire as it passes from host to host" and "does not have the facility to control which hosts or users can access a site."
Lock the Wire: HTTPS, the CA Cert, and Redirects
Serve the repo over TLS and point the client at a certificate authority it
trusts. If you run an internal CA, Munki looks for the CA bundle at
/Library/Managed Installs/certs/ca.pem by default, or wherever
SoftwareRepoCACertificate says, with
SoftwareRepoCAPath available for a directory of certificates. The
SoftwareRepoURL itself should start with https://,
and if you ever plan to use client certificates it must.
By default, Munki refuses to follow
redirects. The FollowHTTPRedirects preference accepts
none, https, or all, and the middle
value is the sane one: it lets a load balancer bounce a client to a secure
host while refusing any redirect that would downgrade the connection to plain
HTTP. Setting it to
all hands a stray redirect the power to move your fleet to a
server you do not control.
Prove the Client: Basic Auth or Certificates
Munki gives you two ways to make the server ask "who are you?" before it hands over a manifest.
| HTTP basic auth | SSL client certificates | |
|---|---|---|
| Client setting |
AdditionalHttpHeaders array carrying
Authorization: Basic and a base64 username:password
|
UseClientCertificate true, PEM at
/Library/Managed Installs/certs/
|
| Server side |
An .htpasswd file and Require valid-user in
the repo's directory block
|
Your own CA, a secure virtual host, and SSLRequire rules
matching the certificate's DN
|
| Per-device identity | Only if you issue one password per client |
Built in; UseClientCertificateCNAsClientIdentifier can even
pick the manifest from the cert
|
| Revocation | Change the password, redeploy the header | Revoke the certificate at the CA |
| Setup effort | Minutes | The wiki's own demo scripts come with a disclaimer |
Basic auth is what the documentation itself calls "much easier to set up," and
paired with HTTPS and a signed server certificate it rates as "reasonably
secure." The catch is where the password lands. Written to
/Library/Preferences/ManagedInstalls.plist, the header is
readable by every user on the Mac, so any of them can lift the credential and
browse the repo. The documented fix: write
AdditionalHttpHeaders into root's preferences at
/private/var/root/Library/Preferences/ManagedInstalls.plist,
which non-admins cannot read, or deliver it in a configuration profile from
your MDM. Either way, confirm the client sees it with
sudo managedsoftwareupdate --show-config.
"Setting up an SSL-secured webserver (one that requires clients to have certificates to connect) is probably the way to go, but setting this up is tricky."
Greg Neagle creator of Munki, on the munki-dev list in October 2010, as archived at Google Groups
Certificates are the stronger lock
because they identify the device rather than a shared secret, and Munki 7.3, released in August 2026, added ClientCertificateAcceptableCAs so the
client can pick the right identity even behind load balancers that never
advertise their CA list. But the wiki's client certificate walkthrough still
carries the warning that its scripts are "only intended to exemplify" the
setup, and its sample certificates are 1024 bits, which newer Apache builds
reject. With an MDM that can install identities, place the PEM that way and skip the scripts. Without one, basic auth in root's preferences is the honest choice.
Verify the Payload: installer_item_hash and hash_strict
When makepkginfo or munkiimport builds a pkginfo, it
writes an installer_item_hash, the SHA-256 of the installer item.
On the client, PackageVerificationMode decides what to do with
it. The default, hash, verifies a download only when the pkginfo
carries a hash. hash_strict verifies every download and fails any
item that has no hash to check. none skips the check entirely,
and the FAQ marks that one "not recommended."
The default is the polite setting: it never breaks an old pkginfo that
predates hashing. It is also the setting that lets a hand-edited pkginfo with
the hash key deleted install whatever now sits at that path. Turn on hash_strict, then re-import anything that fails, because a missing hash is a finding, not an inconvenience. The hash proves the installer
matches the pkginfo. It does not prove the pkginfo, the catalog, or the
manifest match what you wrote. That integrity rests on the wire lock and the
pen lock, never on this one.
PackageVerificationMode, mode by mode
| Value | Behavior | When an item has no installer_item_hash |
|---|---|---|
none |
No integrity check at all | Installs |
hash (default) |
Checks the SHA-256 when one is present | Installs |
hash_strict |
Checks the SHA-256 on every download | Fails with an integrity error |
An "integrity check failed" message means the downloaded item's SHA-256 does not match the pkginfo. The FAQ's first suggestion is to re-import the item so the hash is regenerated, not to delete the key.
Guard the Pen: Who Writes to the Repo
Clients read over HTTPS. Admins write somewhere else entirely, and that
somewhere is the lock Munki leaves to you. The admin tools reach the repo
through a repo plugin. The default, FileRepo, handles a local
folder or a file share reached by an afp://, smb://,
or nfs:// URL, so the write boundary is simply whoever can mount
that share. An https:// repo URL means a custom plugin such as
MWA2APIRepo, where the API's own authentication becomes the
boundary.
The project's Git guide is the cleanest answer to the pen question. Serve the repo over HTTP or HTTPS, keep write access to the Git repository on SSH with key authentication, and let every manifest change arrive as a commit with a name on it. The guide lists a benefit that reads like a security control because it is one: "a change in the repo is not immediately available to clients." A review step between the commit and the deploy is the difference between a typo and an incident. Its one blunt rule: "Version control is not an alternative for backups. And backing up is not an alternative for version control. Use both."
Serve From the Cloud Without Opening the Bucket
A repo in S3, Google Cloud Storage, or Azure Storage needs a signature the bucket will honor. That is what Munki middleware is for:
a module that "can alter an HTTP(S) request to work with servers (often
cloud-based) that require specific headers, keys, or encrypted/signed
requests." Only a single module loads. In Munki 7 it is a
.plugin dylib in /usr/local/munki/middleware. In the
Python era it was a middleware*.py file in
/usr/local/munki, and the documentation told you to
chown root and chmod 600 it precisely because it
holds credentials.
The CloudFront middleware shows the shape of a good one. It signs each request
with a private key, every signature carries an expiry, and the default
lifetime is 60 minutes. The key sits at
/usr/local/munki/munkiaccess.pem owned by root with mode 400, and
AWS allows two CloudFront keys per account so you can rotate without downtime.
Since version 1.1 the certificate and settings can ride in a configuration
profile, which turns key rotation into an MDM push. Treat that private key
like the repo password: it is the client lock, wearing a cloud uniform.
Middleware modules ported to Munki 7
| Module | Backend | What it signs or adds |
|---|---|---|
| CloudFrontMiddleware | Amazon CloudFront in front of S3 | Signed URLs with an expiry |
| S3Middleware | Amazon S3 direct | AWS request signatures |
| GCSMiddleware | Google Cloud Storage | Signed requests |
| AzureStorageMiddleware | Azure Blob Storage | Shared access signatures |
| BunnyNetMiddleware | Bunny.net CDN | Token authentication headers |
| DemoMiddleware | None | Prints the request; a template for your own |
The Munki wiki notes these ports are "believed to work" but "not officially supported at this time," and asks the admins who run them to take over maintenance.
Where the Secrets Live on the Mac
Every lock above ends in a preference on the client, and Munki reads
preferences from four places in a fixed order. A managed profile wins over
root's ByHost plist, which wins over root's plist, which wins over the
world-readable /Library/Preferences/ManagedInstalls.plist. Put
the repo URL, the auth header, and the verification mode in a profile or in
root's file, and leave the world-readable file for the settings Managed
Software Center needs to read as the logged-in user. Edit them with defaults or a profile, never a text editor, because macOS caches preferences and may ignore the change.
Two older protections deserve a mention because they are on by default.
Preflight and postflight scripts must be owned by the user running
managedsoftwareupdate, share its group or wheel, and
must not be world-writable, or Munki refuses to run them. And every official
Munki 7 release is signed and notarized by Mac Admins Open Source, so the tool itself can be verified.
Where Munki reads its preferences, highest precedence first
| Location | Who can read it | Best for |
|---|---|---|
| MCX or configuration profiles | Managed by MDM | Everything, including AdditionalHttpHeaders |
/private/var/root/Library/Preferences/ByHost/ManagedInstalls.XXXXXX.plist
|
Root and admins | Rare per-host overrides |
/private/var/root/Library/Preferences/ManagedInstalls.plist
|
Root and admins | Repo URL, auth header, verification mode |
/Library/Preferences/ManagedInstalls.plist |
Every user on the Mac |
Settings the GUI must read, such as ManagedInstallDir,
HelpURL, and the MSC keys
|
Preferences set in root's file silently override the same keys in the world-readable file, which the wiki flags as a common source of confusion when two admins look in different places.
Frequently Asked Questions
Frequently Asked Questions
- Does Munki need HTTPS?
- It works over plain HTTP, but the project treats HTTPS as the baseline for any repo that holds licensed software, and both authentication methods assume it. Basic auth over unencrypted HTTP is described as trivially sniffable, and client certificates require an https:// repo URL outright.
- Is basic auth or a client certificate more secure for Munki?
- Client certificates identify each device and can be revoked individually, so they are the stronger lock. Basic auth is a shared secret that is much easier to deploy and, with HTTPS and the header stored in root's preferences or a profile, is documented as reasonably secure.
- What does hash_strict change?
- The default hash mode verifies a download only when the pkginfo has an installer_item_hash. hash_strict verifies every download and fails any item without a hash, so a pkginfo that lost its hash cannot install.
- Does Munki verify manifests and catalogs?
- No. The integrity hash covers installer items only. Manifests, catalogs, and pkginfo files are protected by TLS in transit and by whatever controls write access to the repo at rest.
- Can I keep the repo password out of the world-readable plist?
- Yes. Write AdditionalHttpHeaders to root's preferences at /private/var/root/Library/Preferences/ManagedInstalls.plist, or deliver it in a configuration profile. Both take precedence over the /Library copy.
About Munki Repo Security
- Munki wiki: Using Basic Authentication, the htpasswd and AdditionalHttpHeaders walkthrough
- Munki wiki: Using Munki With SSL Client Certificates, the CA and secure virtual host demo
- Munki wiki: Preferences, every ManagedInstalls key and the precedence order
- Munki wiki: Middleware, signed cloud requests for Munki 7 and earlier
- Munki wiki: Munki With Git, version-controlled repos with SSH-only writes
Munki is an open source project created by Greg Neagle and maintained on GitHub, with signed and notarized releases published through Mac Admins Open Source.
Four locks, three of them a preference key away and the fourth a decision about who holds the pen. Turn them and the plain text folder that makes Munki so easy to read stays readable by you alone. For the wide-angle view of how those pkginfo, catalog, and manifest files fit together in the first place, start with our guide to Munki and plist files. And if the web server under your repo could use a second set of eyes, our cybersecurity team hardens servers for a living.
Sources: Munki wiki, Using Basic Authentication, Munki wiki, Using Munki With SSL Client Certificates, Munki wiki, Preferences, Munki wiki, Supported Pkginfo Keys, Munki wiki, Middleware, Munki wiki, Repo Plugins, Munki wiki, Munki With Git, Munki wiki, FAQ, Munki wiki, Release Notes, Munki releases on GitHub, CloudFront Middleware README, munki-dev, How to protect munki repo (October 2010)