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: HTTPS on the wire, client authentication, package hash checking, and control over who can write to the repo. A fresh install enables only the hash check, and only for packages that carry a hash. The rest is up to you, and it matters because a Munki repository is a folder of plain text plists on a web server, and every Mac in your fleet installs whatever those files tell it to. Anyone with the URL can read them. Anyone with the file share can rewrite them. Each lock is a documented preference key or a server directive, so none of them needs a purchase order. The table maps the four, and the sections after it cover each in turn.
| 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 write path | Anyone rewriting a manifest or catalog | File share permissions, Git over SSH, repo plugins | Whatever your file server allows |
The Munki Trust Map
Link to section: The Munki Trust Map
The rows do not cover for one another. HTTPS protects the wire but has no idea who is standing at the far end of it. Client authentication decides who may read the repo, and says nothing about whether the files were edited at rest. Package hashes prove the installer matches its pkginfo, but the pkginfo itself carries no hash, so a rewritten catalog passes every check Munki runs. That gap is why the fourth lock is the one Munki cannot set for you. It lives in your file server, your Git host, or your repo plugin, and it is only as strict as the write permissions you grant there.
Munki's own documentation states the limit directly. 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." Encryption seals the envelope without ever checking the guest list, which is what the three locks after it are for.
Lock the Wire: HTTPS, the CA Cert, and Redirects
Link to section: Lock the Wire: HTTPS, the CA Cert, and Redirects
Serve the repo over TLS and point the client at a certificate authority it
already trusts. Running an internal CA? Munki looks for the CA bundle at
/Library/Managed Installs/certs/ca.pem by default, or wherever
SoftwareRepoCACertificate points, with
SoftwareRepoCAPath on hand if you would rather offer a whole
directory of certificates. The SoftwareRepoURL itself should
begin with https://, and the moment client certificates enter the
picture, it must.
Out of the box, Munki refuses to follow redirects at all. The
FollowHTTPRedirects preference accepts none,
https, or all, and the middle value is the sensible
one. It lets a load balancer hand a client to another secure host and rejects
any redirect that would downgrade the connection to plain HTTP. Set it to
all and a single stray redirect can send your entire fleet to a
server you have never met.
Prove the Client: Basic Auth or Certificates
Link to section: 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. One is a password the whole fleet shares. The other is a certificate issued to one device.
| 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 earns a "reasonably
secure." The weak point is where that password is stored. Written to
/Library/Preferences/ManagedInstalls.plist, the header is
readable by every user on the Mac, so any of them can copy the credential and
go window shopping in your repo. The documented fix is to 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 name the device instead of
passing around a shared secret, and Munki 7.3, released in August 2026, added
ClientCertificateAcceptableCAs so a client can pick the right
identity even behind load balancers that do not advertise their CA list. The
wiki's client certificate walkthrough still carries a warning: the scripts
are "only intended to exemplify" the setup, and the sample certificates are
1024 bits, which newer Apache builds reject. If your MDM can install
identities, place the PEM that way and skip the scripts. If it cannot, basic
auth in root's preferences is the sound choice. A plain lock that is turned
beats a better one still waiting on a free weekend.
Verify the Payload: installer_item_hash and hash_strict
Link to section: 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 fingerprint of the
installer item. On the client, PackageVerificationMode decides
what to do with that fingerprint. The default, hash, checks a
download only when the pkginfo brought a hash along. hash_strict
checks every download and fails any item that shows up without one.
none skips the check entirely, and the FAQ files that option
under "not recommended," which is documentation for "please don't."
The default is the polite setting. It never breaks an old pkginfo from before
hashing was a habit. It also lets a hand-edited pkginfo with its hash key
deleted install whatever now sits at that path, unchecked. Turn on
hash_strict, then re-import anything that fails, and treat each
missing hash as a finding to explain. One boundary to keep in view: the hash
proves the installer matches the pkginfo. It does not prove the pkginfo, the
catalog, or the manifest still say what you wrote. That job belongs to HTTPS
in transit and to write permissions at rest.
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. Deleting the key to clear the error is taking the battery out of the smoke detector.
Guard the Write Path: Who Can Change the Repo
Link to section: Guard the Write Path: Who Can Change 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 whoever can mount that share. An
https:// repo URL means a custom plugin such as
MWA2APIRepo, where the API's own authentication draws the line
instead.
The project's Git guide is the cleanest answer to who gets to write. 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 one benefit that works as a security control: "a change in the repo is not immediately available to clients." A review step between commit and deploy is the distance between a typo and an incident. Its bluntest rule covers the other failure: "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
Link to section: 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, and 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. Back 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 for one reason: 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, so a leaked URL goes stale before lunch. 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 a
outage. Since version 1.1 the certificate and settings can ship in a
configuration profile, which turns key rotation into an MDM push. Treat that
private key like the repo password, because it does the same job: it is the
client lock for a cloud repo.
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. Consider it a volunteer sheet with your name already pencilled in.
Where the Secrets Live on the Mac
Link to section: 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
outranks root's ByHost plist, which outranks root's plist, which outranks 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 has to read as the logged-in user. Edit them with
defaults or a profile, never a text editor, because macOS caches
preferences and will cheerfully ignore your handiwork.
Two older protections deserve a mention because they are already switched on.
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
that checks your software is itself checkable.
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. The wiki flags this as a classic source of confusion when two admins stare at two different files and both swear theirs is the truth.
Frequently Asked Questions
Link to section: Frequently Asked QuestionsFrequently Asked Questions
- Does Munki need HTTPS?
- It runs over plain HTTP, but the project treats HTTPS as the floor for any repo holding 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 one at a time, so they are the stronger lock. Basic auth is a shared secret that is far easier to deploy, and with HTTPS plus the header tucked into root's preferences or a profile, the documentation rates it reasonably secure.
- What does hash_strict change?
- The default hash mode verifies a download only when the pkginfo carries an installer_item_hash. hash_strict verifies every download and fails any item without one, so a pkginfo that misplaced its hash cannot install anything.
- Does Munki verify manifests and catalogs?
- No. The integrity hash covers installer items only. Manifests, catalogs, and pkginfo files lean on TLS in transit and on 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 outrank the /Library copy.
About Munki Repo Security
Link to section: 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 are a preference key away, and the fourth is a decision about who can write to the repo. Set all four and the plain text folder that makes Munki such a pleasure to read stays a pleasure to read for exactly one audience: you. 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)