Skip to content

Route MSIs correctly, verify payload hashes, honour or remove dead settings, make --status read-only - #54

Merged
rodchristiansen merged 1 commit into
mainfrom
fix/integrity-config-status
Oct 4, 2026
Merged

rodchristiansen merged 1 commit into
mainfrom
fix/integrity-config-status

Conversation

@rodchristiansen

Copy link
Copy Markdown
Contributor

Closes #53, #33, #27, #45.

MSI routing (#53)

  • Arguments: MSI arguments are msiexec arguments, so an item that has any goes to msiexec. Before, a Cimian-built MSI with /qn went to sbin-installer, which read /qn as the package path.
  • sbin-installer itself: it never installs itself. It is recognised by any published file name (sbin-installer-*, SbinInstaller-*) or display name.
  • Tests: the routing decision lives in InstallerRouting and is unit-tested.

Payload integrity (#33)

  • Hash check: a manifest hash (SHA-256, optionally prefixed with sha256:) is verified after download for every item type, preflight scripts included. A mismatch, or a hash that is not SHA-256, fails the item.
  • Unpinned items: items without hash log their SHA-256 at debug level.
  • Chocolatey: --ignore-checksums is gone. An item can opt out with "ignoreChecksums": true, and the run logs a warning when it does.
  • Not done: Authenticode for ps1, nupkg and pkg. Today's ps1 payloads are unsigned, so a signature gate would fail them; the hash check covers those types instead.

Configuration (#27)

Setting Now
DryRun Honoured like --dry-run: refuses to run (exit 1). There is no simulated install.
NetworkTimeout Manifest request timeout, and the download stall timeout. Default 120 s, clamped 10-600.
DialogIcon Passed to the dialog.
EnableDialog false turns the dialog off, as NoDialog does.
SilentMode, VerboseMode Taken from policy or settings; a CLI switch cannot turn them off.
FollowRedirects, Reboot Removed from the config, the ADMX and the GUI. Neither was ever implemented. Redirects are always followed and the machine is never restarted. A value still set to true is logged as ignored.

--status (#45)

  • Read-only: handled before the elevation check, opens no session, never prompts.
  • Manifest: the CLI manifest is now asInvoker, so --status, --last-run and --version work for any user.
  • Install runs: they check elevation themselves. Interactive runs relaunch elevated with runas. With no interactive console, the run exits 3 instead of hanging on stdin. The GUI already launches the CLI with runas.
  • Status path: --status and --clear-status showed and deleted ProgramData\BootstrapMate\status.json. They now use the real path, ProgramData\ManagedBootstrap\status.json.

Bugs caught by the signed test, fixed here

  • XML comment in the manifest: the new app.manifest comment contained --, which is invalid inside an XML comment. The exe failed to start with 14001 (side-by-side configuration).
  • Static initialisation order: ConfigManager.Instance was built before RetiredSettingNames was assigned, so every run threw. A regression test now covers it.

Tests

64/64 unit tests pass. The signed x64 build was tested on an already-provisioned Windows 11 PC; machine state was restored afterwards.

Unelevated, stdin closed:

Command Exit Time
--status 0 1.0 s
--last-run 0 1.0 s
--version 0 1.1 s
--url … (install) 3 1.1 s, with "no interactive console"

Elevated:

Run Exit Result
sbin-installer MSI with ["/qn"] 2 overall Went to msiexec, exit 0, installed. The pinned ps1 passed its hash and ran. The tampered ps1 failed with a SHA-256 mismatch and never ran.
Preflight with a wrong hash 1 Status failed; the setupassistant item did not run.
NetworkTimeout=15, stalled download 2 Each attempt failed at 15 s ("no data for 15 seconds"), with three attempts. Reboot=1 was logged as ignored.
DryRun=1 in machine settings 1 Refused before downloading anything.

The last change, warning only when a retired setting is true, was built and unit-tested but not re-run in the signed pass.

…ttings, make --status read-only

MSI routing (#53). A Cimian-built MSI item with arguments was sent to
sbin-installer, which read "/qn" as the package path. sbin-installer was
also asked to install itself when it was published as SbinInstaller-*.msi
rather than sbin-installer-*.msi. MSI arguments are msiexec arguments, so
an item that has them goes to msiexec. So does the sbin-installer package,
under any of its names.

Payload integrity (#33). A manifest "hash" (SHA-256, optionally prefixed
with "sha256:") is verified after download for every item type, preflight
scripts included. A mismatch, or a hash that is not a SHA-256 digest, fails
the item. Chocolatey no longer runs with --ignore-checksums; an item opts
out with "ignoreChecksums": true and the run logs a warning.

Dead settings (#27). DryRun is honoured the way --dry-run is: the run
refuses. NetworkTimeout sets the manifest request timeout and the download
stall timeout (default 120 s, clamped 10-600). DialogIcon reaches the
dialog, EnableDialog=false turns the dialog off, and SilentMode and
VerboseMode from policy or settings take effect. FollowRedirects and Reboot
were never implemented. They are removed from the configuration, the ADMX
and the GUI, and a value still set to true is logged as ignored.

--status (#45). It runs before the elevation check, opens no session and
never prompts. The CLI manifest is now asInvoker, so the read-only
switches work for any user. An install run checks elevation itself: it
relaunches elevated when it can, and exits 3 without prompting when there
is no interactive console. --status and --clear-status also now use the
real status.json path under ProgramData\ManagedBootstrap.

Closes #53
Closes #33
Closes #27
Closes #45
@rodchristiansen
rodchristiansen merged commit a92bdcf into main Oct 4, 2026
2 checks passed
@rodchristiansen
rodchristiansen deleted the fix/integrity-config-status branch October 4, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MSI arguments are passed to sbin-installer, and sbin-installer installs itself

1 participant