Skip to content

Security and Package Verification

Rod Christiansen edited this page Sep 3, 2026 · 1 revision

Security and Package Verification

BootstrapMate downloads a manifest over the network and then downloads and executes everything that manifest names, as root, on a Mac that has just been enrolled. This page states exactly what is verified before that happens, what is not, and how to configure the controls.

Threat model

The manifest is the whole instruction set. Anyone who can change the manifest — by compromising the origin, the CDN in front of it, or the transport — chooses which packages get installed and which scripts get run as root on every Mac that enrolls until you notice. The manifest is fetched with caching disabled at both ends, so a stale copy cannot mask a change, but nothing about the fetch establishes who wrote the manifest. Trust in the manifest is entirely trust in the origin and the transport.

Payload integrity is a separate question. Each item in the manifest carries a SHA-256 hash, and the download is retried until the bytes on disk match it. That check proves a payload matches what the manifest asked for; it proves nothing about whether the manifest itself is legitimate, because an attacker who rewrites the manifest rewrites the hashes with it. The only control that binds an installer to an identity outside the manifest is package signature verification, which is why it is on by default. Scripts have no equivalent control.

Package signature verification

Before an item of type package is handed to /usr/sbin/installer, BootstrapMate runs:

/usr/sbin/pkgutil --check-signature <package>

A non-zero exit is treated as untrusted, meaning either no signature or a signature macOS will not trust. On success, BootstrapMate parses the first ten-character Team ID it finds in parentheses in the output — the leaf certificate line, for example 1. Developer ID Installer: Example Corp (AB12CD34EF).

The decision is then:

Result allowUnsigned off allowUnsigned on
Trusted signature, Team ID matches or none required Install Install
No signature, or a signature macOS does not trust Refuse Install, with a warning logged
Trusted signature, Team ID does not match expectedTeamID Refuse Refuse

A refusal logs one of these and fails the item:

Refusing to install <pkg>: untrusted or missing signature (<reason>)
Refusing to install <pkg>: Team ID mismatch — found X, expected Y

Verification is controlled by the verifyPackageSignatures preference, which defaults to true, or by --no-verify-signature, which turns it off. Passing neither leaves the preference in charge; the flag can only disable verification, never force it on. See Preferences and Command Line Reference.

Setting an expected Team ID

Set expectedTeamID in the profile, or pass --expected-team-id, to the ten-character Apple Team ID that signs your packages. The value is trimmed and upper-cased before comparison, so stray whitespace and lowercase in an MDM field are tolerated.

With no Team ID configured, any signature macOS trusts is accepted — which includes every Developer ID in the world, not only yours. Setting the Team ID is what turns the check from "signed by someone" into "signed by us".

A manifest item may carry its own expectedTeamID, which overrides the global value for that item. That is the mechanism for admitting a third-party vendor package into an otherwise single-signer run.

A Team ID mismatch is never overridable. allowUnsigned does not bypass it, and neither does a per-item allowUnsigned.

What allowing unsigned packages actually permits

Setting allowUnsigned to true, globally or on one item, means BootstrapMate will install a package that carries no signature at all, or one whose certificate chain macOS rejects — for example expired, revoked, or issued by a CA the Mac does not trust. The package still has to match its SHA-256 hash, and it is still installed as root. The only remaining barrier between a tampered manifest and root execution is your control of the manifest origin.

Scope it as narrowly as you can. Prefer the per-item field over the global preference, so one in-house package built without a Developer ID does not open the door for every other item in the run.

Hash checking

Every manifest item requires a hash: a lowercase hex SHA-256 of the file. BootstrapMate computes the digest in chunks and compares it after each download attempt, retrying up to retries times with retrywait seconds between attempts. When the digest still does not match it logs All retries failed for <path>. and the item fails.

Two consequences are worth knowing. First, if the file is already on disk with a matching hash the download is skipped entirely — Already have valid file: <path>. Skipping re-download. — so changing a payload without changing its hash in the manifest means the change never reaches a Mac that already has the old file. Second, HTTP status codes are never inspected: a 404 error page is written to the destination path and then fails the hash check, so a misconfigured host usually surfaces as Hash mismatch, not as a 404.

What is not verified

Be clear about the boundaries of the checks above.

  • Scripts are never signature-checked. Items of type rootscript and userscript are verified by SHA-256 only, then executed. A rootscript runs as root. Items of type userscript also run as root, in the daemon context, not as the logged-in user.
  • The manifest itself is not signed or hash-checked. There is no signature over the manifest and no pinned digest for it.
  • The Authorization header is not scoped to a host. The configured header is attached to the manifest request and to every payload download in the manifest, whatever host the URL names. A manifest that points an item at a third-party host sends your credential there.
  • HTTP responses are not inspected for status. See above.
  • There is no certificate pinning on the manifest or payload requests.

Transport

Serve the manifest and every payload over HTTPS. Nothing in the tool forces this, and the combination of an unsigned manifest, hashes chosen by that manifest and root execution means a plaintext manifest hands complete control of the Mac to anyone on the path. Payload verification cannot make up for it, because the attacker who rewrites the manifest also rewrites the hashes. See Serving Manifests and Packages.

The bundled helper's Team ID placeholder

The privileged XPC helper in this repository validates its clients against a hardcoded placeholder Team ID, TEAMID0000. Because no real signed build carries that Team ID, the helper refuses every connection as shipped, and the graphical app reports that it has no connection to the helper. The CLI, which is what runs during enrollment, is unaffected — it does not use the helper. If you intend to use the app, edit that requirement to your own Team ID and build from source. See Building and Signing.

See also

Clone this wiki locally