Skip to content

Security and Package Verification

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

Security and Package Verification

BootstrapMate downloads a document from a URL and installs whatever that document lists, with a fully elevated token. This page states exactly which of those bytes are verified, how, and what is trusted without being checked.

Threat model

Everything BootstrapMate acts on arrives over the network. The manifest comes from a URL supplied by policy, by the registry, by a command-line switch, or from a URL compiled into the binary. The manifest names package URLs, and those may point anywhere — there is no requirement that they share a host with the manifest. Whoever can change the manifest document, or serve a different response for a package URL, chooses what gets installed. Whoever can change the manifest URL — which is a registry value under HKLM — chooses the manifest.

The consequence of a successful substitution is total. BootstrapMate runs as Administrator (and, under its normal deployment, as SYSTEM from a scheduled task), and it hands each downloaded file to msiexec, to powershell.exe -ExecutionPolicy Bypass -NoProfile, to installer.exe, to choco, or executes it directly — all inheriting that elevated token. An attacker-supplied file is not sandboxed, sampled, or reviewed; it is executed. The Authenticode gate described below exists to close exactly one part of this: it makes the question "did a publisher I trust produce this binary" answerable independently of "did the manifest host tell me to install it".

What is verified

Downloaded installers of type msi and exe are checked with Authenticode before they are executed.

SignatureVerifier.VerifyFile P/Invokes WinVerifyTrust with the WINTRUST_ACTION_GENERIC_VERIFY_V2 action GUID ({00AAC56B-CD44-11d0-8CC2-00C04FC295EE}), WTD_UI_NONE, WTD_CHOICE_FILE and WTD_SAFER_FLAG. A non-zero result means the file is unsigned, tampered with, or does not chain to a trusted root, and yields the status Untrusted with the detail WinVerifyTrust=0x{hr:X8}.

If the trust check passes and an expected publisher is configured, the signer certificate's subject is read with X509Certificate.CreateFromSignedFile, the CN= value is extracted (falling back to the whole subject if no CN is present), and the expected value must appear inside it, compared case-insensitively. This is a substring test, not an exact compare — Example matches Example Corporation. A failure yields the status PublisherMismatch.

The result is then turned into a decision:

Status AllowUnsigned off AllowUnsigned on
Trusted Allow Allow
Untrusted Deny Allow, with a warning in the log
PublisherMismatch Deny Deny

A publisher mismatch is never bypassed. AllowUnsigned covers only the missing-or-untrusted case; an explicit mismatch is treated as a tampering signal.

On a deny the run logs Signature check failed for {file}: {reason} and Refusing to install {file} — {reason}, then throws Refusing to install {file}: signature verification failed. That exception is caught by the per-item handler, so the item is marked failed, its downloaded file is kept in C:\ProgramData\ManagedBootstrap\cache for inspection, and the run continues to the next item — and still exits 0. See Command Line Reference.

Configuring the gate

Three settings control it, all documented in Preferences:

  • VerifyPackageSignatures — enabled by default. Setting it to 0 skips verification entirely for every item.
  • ExpectedPublisher — when set, requires the signer to contain this string.
  • AllowUnsigned — permits an untrusted or unsigned installer.

A manifest item may override the last two per item with its own expectedPublisher and allowUnsigned fields; see Manifest Reference. An item-level allowUnsigned: true therefore weakens a machine-wide policy for that one item, which is worth knowing if you treat the manifest as less trusted than your policy.

What is not verified

Revocation is deliberately not checked. The verifier passes WTD_REVOKE_NONE. The source records the reason: BootstrapMate runs during OOBE (out-of-box experience) and the Autopilot ESP (Enrollment Status Page), where networking is often unavailable, and an online OCSP or CRL lookup would add latency and could hard-fail a valid installer when the revocation endpoint is unreachable. The chain to a trusted root is still verified. A certificate that has been revoked but not expired will still pass.

There is no hash verification of downloads. Nothing in the tool computes or compares a digest of a downloaded file against a value in the manifest. The hash field that appears in the shipped example manifest is never read. If you need content pinning, Authenticode is the only mechanism available here.

Only msi and exe are gated. Items of type ps1/powershell, nupkg and pkg are downloaded and executed without any signature check:

  • A ps1 item is run as powershell.exe -ExecutionPolicy Bypass -NoProfile -File, so the machine's execution policy does not constrain it either.
  • A nupkg item goes to sbin-installer, or falls back to Chocolatey. The Chocolatey command line includes --ignore-checksums, so Chocolatey's own checksum validation is disabled too.
  • A pkg item goes to sbin-installer, whose own verification behaviour is outside this tool.

This is the boundary to hold in mind: for MSI and EXE, provenance is checked and the manifest is only trusted to choose an installer. For scripts and for the two package formats above, the manifest is trusted completely — it names a URL and the bytes at that URL are run with SYSTEM privileges, unexamined.

The manifest itself is not verified. It is fetched over HTTP(S) and parsed. There is no signature over the manifest document, no pinning of its host, and no validation beyond JSON or YAML well-formedness.

Transport

Use HTTPS for the manifest URL and for every package URL. Nothing in the tool requires it: the URL is passed to HttpClient as given, and an http:// URL is fetched without complaint. Over plain HTTP, both the manifest and any Authorization header attached to that request travel in the clear, and any network position between the device and the host can substitute the manifest — which, for the unsigned item types above, is equivalent to remote code execution as SYSTEM.

TLS trust is the platform's: certificate validation follows the machine's trust store and schannel defaults, and the tool neither pins a certificate nor relaxes validation. Requests carry User-Agent: BootstrapMate/{version}.

Redirects are followed, because a default HttpClient follows them. The FollowRedirects setting does not change this — see Serving Manifests and Packages.

The credential

The AuthorizationHeader value is sent verbatim as the Authorization header on the manifest request, and on a package download only when the package URL's host matches the manifest URL's host, compared case-insensitively. Anything hosted elsewhere is fetched without it and the run logs Authorization header withheld for cross-host download: {url} at Debug level.

That scoping is a security property, not an optimisation: it stops an org credential being handed to whatever third-party host a manifest entry happens to name. Keep it in mind when you split a manifest and its payloads across hosts — an authenticated package host that is not the manifest host will receive anonymous requests and reject them.

The header lives in the registry, and under policy it is readable by anything running with sufficient privilege on the device. Scope the token it carries accordingly: read-only, to the paths that serve your manifest and packages, and rotatable.

See also

Clone this wiki locally