Security notes¶
- No secrets in JSON. Passwords are read from environment variables loaded from
.env..envand real client configs are gitignored. - autounattend.xml contains a plain-text password. This is unavoidable for unattended OOBE. Mitigations: use a per-batch temporary local-admin password, store the answer file only on removable media, and wipe/rotate after deployment. Never commit
autounattend.xml. - Auto-logon during setup stores the resume password as an LSA private secret (
Totl.Security\Set-TotlAutoLogon), not as a plaintextDefaultPasswordregistry value, and bounds the window via WinlogonAutoLogonCount. A SYSTEM credential-cleanup watchdog guarantees the credentials are cleared even if a run crashes before finalize. To force cleanup manually:Invoke-Provision.ps1 -ClearCredentials(or-Finalize). - URL installers support an optional
sha256field — set it to verify downloads before executing. - Least privilege. The toolkit must run elevated; it does not create additional privileged accounts beyond the configured local admin.
- Third-party scripts. The optional winutil path executes a live remote script. It is off by default; prefer the native baked-in tweaks for production.
Zero-knowledge secret escrow (backend)¶
Admin passwords and BitLocker recovery keys are escrowed to the Cloudflare backend, but the backend never holds a decryption key and can never read them.
- Encrypt-only engine. Each machine fetches the tenant public key (
GET /v1/tenant/pubkey, a JWK) and encrypts the secret with RSA-OAEP-SHA256 (Totl.Crypto\Protect-TotlSecret). Only base64 ciphertext is uploaded (POST /v1/secret). - Browser-only decryption. The tenant private key is generated in the engineer's browser
(
portal/crypto.js), wrapped with an org passphrase (PBKDF2-SHA256 → AES-GCM), and stored only as that wrapped blob. Reveal returns ciphertext; the browser unwraps the key and decrypts locally. - D1 stores ciphertext only for
secrets.ciphertextandtenants.wrapped_private_key. Platform at-rest encryption is assumed but treated as insufficient on its own. - API tokens are hashed (
sha-256(salt + token)), never stored in plaintext — a DB leak cannot replay them. - Every ingest and reveal is audit-logged (append-only
audittable: actor email from the verified Access JWT, action, machine, time, IP).
Data classification (D1)¶
- App-layer encrypted, retrievable: BitLocker keys, local-admin passwords, BIOS passwords, domain/Entra-join creds, RMM keys, Wi-Fi PSKs.
- Hashed, never retrievable: machine API tokens.
- Field-level encrypt (PII): usernames / emails.
- Tenant-isolated + access-controlled only: run status, timings, asset inventory.
- Append-only / tamper-evident: audit log.
Key recovery / break-glass (REQUIRED)¶
Because the backend cannot decrypt, a lost tenant passphrase/private key means all that tenant's escrowed secrets are unrecoverable. Operate a recovery scheme before relying on escrow in production: Shamir secret-sharing (M-of-N) of the tenant passphrase (or the private key), with shares held by separate trusted custodians and/or sealed offline. The portal warns at onboarding that the passphrase cannot be recovered from the server.
Per-machine credentials & hardening (Phase 2, implemented)¶
- Unique per-machine local-admin password. At finalize the engine rotates the local admin to a
random per-box password (
Totl.Crypto\New-TotlPassword) and escrows it per machine — never a shared fleet password (Totl.Enroll\Invoke-TotlEnrollment, gated byidentity.localAdmin.rotatePassword). - LSA-backed autologon. The resume password is written to the LSA private store, never as plaintext
in the Winlogon registry key (
Totl.Security\Set-TotlAutoLogon).security.autoLogon.maxLogonCountbounds how many automatic logons can occur before Windows disables autologon on its own. - Crash-proof credential cleanup. A SYSTEM startup watchdog (
TotlProvision-CredCleanup) runs the orchestrator in-CredentialWatchdogmode and clears autologon credentials if the run has finalized or stalled pastsecurity.credentialCleanup.staleMinutes(a stale state-heartbeat = crash). This closes the gap where a crash before finalize could leave credentials behind. Disable viasecurity.credentialCleanup.enabled: false. - Secret source. With
security.secretSource: "prompt", the engine prompts for the local-admin password at runtime (Read-Host -AsSecureString) instead of reading it from a.envfile — so secrets need not ship on USB media. Default remainsenvfor compatibility. - Pinned downloads. URL installers verify an optional
sha256before running; theTotl.Security\Save-TotlVerifiedDownloadhelper refuses to fetch without a pinned hash unless-AllowUnverifiedis passed. Residual risk: the Chocolatey bootstrap script is fetched live — prefer pre-installing choco on the image, or pin via your own mirror. - Code signing.
engine/scripts/Sign-TotlRelease.ps1Authenticode-signs all scripts and packaged binaries (SHA256 + RFC3161 timestamp) to support moving execution policy toward AllSigned.