Skip to content

Security notes

  • No secrets in JSON. Passwords are read from environment variables loaded from .env. .env and 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 writes DefaultPassword to the Winlogon registry key in plain text so the run can resume across reboots. The orchestrator clears it (Clear-TotlAutoLogon) at finalize. If a run is interrupted, run Invoke-Provision.ps1 -Finalize to clean up.
  • URL installers support an optional sha256 field — 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.ciphertext and tenants.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 audit table: 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 (planned, Phase 2)

  • Each machine gets a unique randomized local-admin password (Totl.Crypto\New-TotlPassword), escrowed per machine — never a shared password across the fleet.
  • Autologon DefaultPassword moves from plaintext registry to LSA-encrypted storage, with a failsafe cleanup task that clears credentials even if a run crashes before finalize.