-
Notifications
You must be signed in to change notification settings - Fork 0
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.
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.
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.
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.
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.
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.
Be clear about the boundaries of the checks above.
-
Scripts are never signature-checked. Items of type
rootscriptanduserscriptare verified by SHA-256 only, then executed. Arootscriptruns as root. Items of typeuserscriptalso 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
Authorizationheader 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.
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 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.