Files
SilverMetal/windows/welcome-app-spec.md
sysadmin 4f3e25e816
All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (pull_request) Successful in 4m37s
docs(welcome): record VM e2e validation + 3 bugs found/fixed + BitLocker-PIN follow-up
2026-06-09 11:02:52 +01:00

12 KiB
Raw Blame History

SilverOS Welcome — Specification (v1)

Status: design approved 2026-06-09. Part of SilverMetal Enhanced — Windows. First-logon onboarding app that turns a hardened image into a personalised device: the user picks a flavour, creates their account + BitLocker PIN, and the app applies the chosen configuration by orchestrating the existing §AH hardening modules.

Bound by hardening-spec.md, iso-builder.md, and the SilverMetal ../docs/design-principles.md.

1. Purpose & positioning

Today the image applies one fixed hardening config at first boot (SetupComplete.cmd → §AH modules). The Welcome app makes "SilverMetal Enhanced — Windows" a selectable platform: a friendly first-logon wizard where the buyer chooses what the device is for (a flavour = config profile + app set), sets up their own account, and the device configures itself accordingly.

It also resolves two real gaps found during VM validation:

  • No throwaway credential in the public repo — the committed account becomes a locked bootstrap account that self-destructs after onboarding.
  • BitLocker PIN enrollment — collected interactively in the wizard (BitLocker TPM+PIN can't be fully automated; this is the right place for it).

v1 = interactive auto-launch only. A fully-silent pre-baked SKU mode is a later phase (§8).

2. Architecture & components

A Blazor Hybrid (.NET MAUI + WebView2) desktop app, published self-contained (bundled runtime) and baked into the image. Reuses SilverSHELL components + the SilverDESK "Welcome" wizard aesthetic (Mercury). Four well-bounded units:

Unit Responsibility Depends on
Welcome.Shell MAUI host + wizard UI (the steps in §3). What the user sees. Welcome.Flavours, Welcome.Apply
Welcome.Flavours Load + validate flavour manifests from windows/flavours/*.json (baked into the image). manifest schema
Welcome.Apply Orchestrator: run the §AH PowerShell modules per the manifest, install the app set, create the real account, enrol BitLocker, tear down the bootstrap. PowerShell modules, Windows account/BitLocker APIs
Welcome.Bootstrap Answer-file / AutoLogon / RunOnce wiring that auto-logs-in and launches the app. autounattend, build.ps1

Each unit is independently testable: Welcome.Apply can run headless against a flavour manifest (no UI); Welcome.Flavours validates manifests without applying; the Shell can be run with a mock applier.

3. Boot → onboard → handoff flow (bootstrap lifecycle)

  1. Image ships a locked bootstrap admin account (sm-bootstrap) with AutoLogon (LogonCount=1) and a RunOnce/custom-shell that launches the Welcome app in a kiosk-ish session (no taskbar/Explorer escape). The §AH modules no longer run from SetupCompleteSetupComplete only stages the payload; the Welcome app applies it.
  2. First logon (auto, as sm-bootstrap) → Welcome app launches. Wizard steps:
    • Welcome — brand intro.
    • What's this device for? — persona → selects a flavour (cards: Privacy-Max / Daily-Driver / Journalist / Developer).
    • Create your account — daily username + password (a Standard User), plus a separate device-administrator password (for the SilverOS Admin elevation account).
    • Secure this device — set a BitLocker PIN (≥ flavour-min).
    • Preferences — a few toggles (e.g. telemetry-floor confirmation, browser default, timezone).
    • Apply — progress UI while Welcome.Apply runs.
    • Done — "restart to finish."
  3. Apply (Welcome.Apply, elevated):
    • run the flavour's hardening modules (per manifest) + app-set install;
    • create the daily Standard account + the separate SilverOS Admin account;
    • enrol BitLocker TPM+PIN with the entered PIN;
    • remove AutoLogon + the RunOnce.
  4. Self-destruct + reboot — delete the sm-bootstrap account, then reboot into the real account (also activates VBS/Credential Guard, which need a reboot).

4. Flavour manifest model

A flavour is a JSON file in windows/flavours/ baked into the image:

{
  "id": "privacy-max",
  "label": "Privacy-Max",
  "description": "Maximum lockdown. Everything Microsoft exposes, turned to its tightest.",
  "hardening": {
    "modules": ["00","01","02","03","04","05","06","07"],   // which §A-H modules
    "params": { "wdac": "enforce", "telemetry": "security" }  // per-module overrides
  },
  "appSet": ["SilverBrowser","SilverVPN","SilverKeys","SilverDuress"],
  "settings": { "defaultBrowser": "SilverBrowser", "autoLock": 120 }
}

Welcome.Apply orchestrates the existing PowerShell §AH modules per hardening.modules/params — PowerShell stays the single source of truth for what hardening does (also reusable headless for the future silent SKU). (Rejected alternative: re-implement hardening in a .NET library — cleaner for an all-.NET app but duplicates the proven logic.) The Shell renders one card per manifest; adding a flavour = adding a JSON file.

Hardened baseline applies to ALL flavours — no flavour is unhardened. Every flavour runs the core §AH baseline (telemetry floor, BitLocker+PIN, VBS/HVCI, firewall default-deny, Defender/ASR, etc.); flavours only tune strictness above that baseline and choose the app set. There is no "off."

Starter flavours (v1): Daily-Driver (the default / recommended — balanced: full baseline, WDAC in audit, everyday app set), Privacy-Max (strictest: WDAC enforce, tightest toggles, removable-storage controls — opt-in, not default), Journalist (privacy + duress/comms emphasis), Developer (baseline + dev tooling allowances, e.g. WDAC audit + dev-signed allowances). The wizard pre-selects Daily-Driver.

5. Account & BitLocker integration

  • The user's daily account is a Standard User (least-privilege by default — a real hardening win, and the SilverLABS default). Elevation uses a separate local Administrator account (SilverOS Admin) created alongside it; the user sets the admin password in the account step (distinct from their daily password). (Single-admin/"personal-device owner" remains available as a flavour/pref option, but is not the default.)
  • BitLocker TPM+PIN is enrolled with the user's PIN during Apply (Enable-BitLocker -TpmAndPinProtector), satisfying module C's interactive step. Recovery key stored offline / shown to the user (never cloud-escrowed) per hardening-spec.md §C.
  • The bootstrap account is admin only so the app can run hardening + manage accounts; it is deleted in step 4 so no standing extra admin remains.

6. Build & repo integration

  • New windows/welcome/ (MAUI app source) + windows/flavours/*.json.
  • build.ps1 gains a stage that drops the published Welcome app + flavours into the image (/Program Files/SilverMetal/Welcome + a flavours dir), and wires the bootstrap (AutoLogon + RunOnce) via the answer file / offline registry.
  • The answer file switches from "create silvermetal/open sesame" to "create sm-bootstrap + AutoLogon + launch Welcome." SetupComplete.cmd stops running the §AH modules directly (the Welcome app runs them).
  • A repo switch (vars.SILVERMETAL_WELCOME_ENABLED, default on) controls whether the build wires onboarding or falls back to today's direct-hardening behaviour (so the current validated path remains available).

7. Testing

  • Welcome.Flavours: unit tests validate every shipped manifest against the schema.
  • Welcome.Apply: headless run against each flavour on the VM (reuses the SLAB01 VM-test harness) → assert the firstboot/apply log shows the expected modules + the real account + BitLocker protector + bootstrap removed.
  • Welcome.Shell: bUnit-style component tests for step navigation/validation; a manual VM pass for the full wizard.
  • The existing offline ISO assertions extend to check the Welcome app + flavours are baked.

8. v1 scope

In: interactive auto-launch onboarding; persona→flavour; account + BitLocker-PIN creation; prefs; apply-via-PowerShell-modules; bootstrap self-destruct; 34 starter flavours; build/answer-file wiring + WELCOME_ENABLED switch.

Out (later phases):

  • Silent pre-baked mode (apply a baked flavour + pre-set account with no wizard — the "we did everything for you" SKU).
  • SilverDESK / fleet enrollment role.
  • Shared flavour model with the Linux line.
  • Real Stack app installs — the app-set installer is wired, but Stack entries remain stubs until native Windows Stack builds exist (M4).
  • Single-admin / "personal-device owner" account model as a selectable option (Standard-user daily + separate admin is the v1 default).

9. Open questions / risks

  • WebView2 + .NET runtime in the image — confirm WebView2 Evergreen is present on IoT Enterprise LTSC (or bundle the fixed-version runtime); self-contained publish covers .NET.
  • Kiosk lock-down of the bootstrap session — ensure the user can't escape the wizard to a privileged shell before the real account exists.
  • Apply failure handling — if a module fails mid-apply, the wizard must surface it and leave the device in a recoverable state (don't delete bootstrap until apply + account creation succeed).
  • Admin-credential UX — confirm the daily/admin two-password step is clear (vs. generating + showing the admin secret once); ensure password strength rules on both.

10. VM end-to-end validation — VALIDATED 2026-06-09

Built the packed ISO via CI (build-iso-windows.yaml, all green: 22 unit/bUnit tests + ISO bake + offline payload assertions) and ran it on SLAB01 VM 102 (UEFI + SecureBoot pre-enrolled keys + vTPM 2.0). Verified end-to-end:

  • Boots UEFI/SecureBoot; forced-legacy hands-off install onto a blank disk; OOBE auto-completes.
  • sm-bootstrap auto-logs in; the Welcome app auto-launches elevated and renders the wizard (Mercury theme, bundled fonts).
  • All four flavours load from the baked flavours/*.json; Daily-Driver pre-selected.
  • Drove the full wizard: Welcome → Flavour → Account (created alice + admin password + BitLocker PIN) → Prefs → Apply → "All Done!".
  • Post-apply, offline + on-login assertions: alice created (Standard daily user) and SilverOS Admin created (separate elevation account) both appear on the login screen; sm-bootstrap removed; no auto-login (AutoAdminLogon disabled → teardown ran); BitLocker enabled (volume encrypted, -FVE-FS-). Account creation + bootstrap teardown require elevation, confirming the app runs elevated.

Three deployment bugs found + fixed during validation (only a real run surfaced them): (1) WebView2 user-data folder defaulted to non-writable Program Files → redirected to LocalAppData; (2) FirstLogonCommands run un-elevated → bake ConsentPromptBehaviorAdmin=0 offline + launch via Start-Process -Verb RunAs; (3) AccountStep Next never enabled because the wizard host didn't re-render on child validity change → added an OnValidityChanged callback. Also a separate CI fix: bUnit tests must reference a Razor Class Library, not the MAUI app assembly (else WindowsAppRuntime DllNotFound on the clean runner).

Follow-ups (not v1-pipeline blockers):

  • BitLocker PIN not enforced — the device booted with no pre-boot PIN prompt (TPM-only auto-unlock). The intended TPM+PIN protector did not take effect, most likely because the FVE "require startup PIN" policy (UseAdvancedStartup/UseTPMPIN) wasn't set before Enable-BitLocker. The user's PIN currently provides no boot protection — fix before shipping.
  • Apply services don't check PowerShell exit codesAccountService/BitLockerService/BootstrapService run powershell.exe but ignore ProcessResult.ExitCode, so silent failures (like the BitLocker PIN degradation) go unsurfaced. They should check exit codes and fail/log loudly.