You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 614eade
Browse filesBrowse the repository at this point in the historyBrowse files
Disable standalone TOTP verification by default
Standalone TOTP verification now requires either a Debug build
or the explicit `--allow-standalone-totp` startup argument.
This change removes support for plaintext TOTP seed storage
(`totp-standalone.json`) in production builds.
- Added `AllowStandaloneTotpVerification` in `GlobalConstants`
with methods to enable it explicitly.
- Modified `Program.cs` to parse and apply the opt-in argument.
- Updated `SecurityCredentialStore.cs` to prevent reading
plaintext seeds unless explicitly allowed.
- Adjusted `SecurityService.cs` to enforce the new policy:
- TOTP-only attempts are blocked unless explicitly enabled.
- Legacy plaintext seeds are deleted on the next save.
- Introduced `TestStandaloneTotpOptIn.cs` to ensure tests
enable the TOTP-only path for coverage.
Behavioral changes:
- Default: TOTP cannot pass verification alone without a
password or other factors.
- Legacy plaintext seeds are ignored and removed on save.
Security improvements:
- Eliminates reliance on plaintext TOTP seeds in production.
- Reduces attack surface by enforcing stricter verification
policies.
Pending:
- Add a head-level toggle for mobile platforms to enable
standalone TOTP debugging.
Copy file name to clipboardExpand all lines: AGENTS.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -164,6 +164,7 @@ Keep this map short and stable. When code moves, AI agents should re-read the mo
164
164
- First-run OOBE is a top-level window shown immediately after `BuildHost()` and before floating-window/main-app initialization. Its carousel begins with a non-blocking, centered welcome screen with the application icon; it is outside the seven configuration pages, whose first item places the collapsed-by-default verifiable-draw notice before the privacy-policy and GPLv3 acknowledgement controls. Each requires explicit acknowledgement. The welcome page owns the lower-left language selector; changing it persists the language, refreshes ViewModel-derived localized values, then recreates only the OOBE visual tree into localized resources without restarting or leaving the first-run flow. An active import drawer defers the visual replacement until its preview/mapping flow closes. `GuideCompleted`, `AcceptedVerificationNoticeVersion`, `AcceptedPrivacyPolicyVersion`, and `AcceptedGplVersion` determine whether an OOBE acknowledgement is required. The fork's `AcceptedSecRandomServicesVersion` is versioned but deliberately excluded from that gate: its acknowledgement is optional during setup and is enforced later at the point of use by `SecRandomServicesConsent`. After the full guide has completed, a verifiable-draw notice, privacy-policy, or GPL version update opens only the acknowledgement page and its privacy controls, not unrelated setup pages. OOBE class/prize spreadsheet imports reuse the list-management import controls in its own right-side drawer, so their file mapping, preview, duplicate handling, and cancel behavior stay aligned with the settings list pages. Completing OOBE continues into normal startup and explicitly opens the primary main window; it must not exit merely because the OOBE window closes. Verifiable-draw notice, privacy-policy, and GPL changes increment their app-layer OOBE versions independently.
165
165
- Settings JSON, full-data ZIP, and backup restore only accept manifest/envelope exports produced by SecRandom v3. Validate `producer_version` before taking an import snapshot or changing data; unsupported sources must be explained in a modal dialog. `producer_version` is the raw `GlobalConstants.Version`, so it keeps a SemVer pre-release/build suffix (`v3.0.0-alpha.2`); the gate must strip that suffix and compare only the numeric core's major, never feed the raw value to `Version.TryParse` — otherwise a pre-release build rejects the archives it produced itself.
166
166
- Security credentials must never be stored in `MainConfigModel` or `settings.json`. Keep passwords, TOTP seeds, USB binding tokens, and lockout state in `SecRandom/Services/Security`'s separate credential store; ordinary settings only select factors and protected operations. The sole credential file is `data/config/security/credentials.json`: its internal `FormatVersion` is authoritative, uses Argon2id-derived key material plus AES-256-GCM for portable storage, and must not depend on DPAPI, Keychain, `secret-tool`, or a versioned filename. Do not read or migrate older credential files. `SecurityCredentialStore` restricts the whole `data/config/security` directory to the current user once per process and on every save (Unix `0700` directory with `0600` files and temporary files; Windows breaks ACL inheritance and keeps only the current user plus SYSTEM and Administrators), because that directory also holds the readable `totp-standalone.json` copy; the hardening is best-effort and must never leave the directory unreadable to the app itself. Lockout state deliberately stays outside the AES-GCM authenticated data — a failed attempt happens without the encryption key, so `FailedAttempts`/`LockedUntilUtc` must remain writable through `SaveMetadata`, which also means anyone who can rewrite the credential file can reset the lockout: the lockout only restrains callers that cannot write that file.
167
+
-**Fork policy: TOTP-only verification is disabled.** A TOTP code can only be checked without the password by keeping a plaintext seed copy, so `GlobalConstants.AllowStandaloneTotpVerification` is `false` outside a debug build and outside the explicit `--allow-standalone-totp` startup switch (wired in `SecRandom.Desktop/Program.Main`). When it is off, `SecurityCredentialStore.LoadStandaloneTotp` returns null unconditionally, `SecurityService` never counts `totpPassed` on its own, and the next credential save deletes any leftover `totp-standalone.json`. USB-only and password-augmented verification are unaffected, and the tests opt in through `SecRandom.Core.Tests/TestStandaloneTotpOptIn.cs` because CI runs Release.
167
168
- While `SecuritySettingsConfig.RequireAllSelectedFactors` is off ("any selected method" mode) every selected factor verifies on its own, and TOTP cannot be checked without its seed, so that mode keeps a password-free seed copy in the sibling `data/config/security/totp-standalone.json` (written only while the mode is off and TOTP is configured; deleted when the mode turns on or the password is removed). The directory stays excluded from every export, cloud backup, and the Android provider, and the settings description discloses the trade-off. Never move that copy into `credentials.json`: its AES-GCM authentication data covers only `FormatVersion`, the password credential, `HasTotp`, and USB bindings, so a readable seed field there would be read before any authentication and would silently bypass the existing tamper detection.
168
169
- `SecuritySettingsConfig.SettingsIntegrityCheckEnabled` (desktop-only, password-gated) records a SHA-256 fingerprint of the whole `data/config/settings.json` in the credential envelope's plaintext `SettingsIntegrity` record on every `MainConfigHandler.Save()`/`Reload()`, and `App` verifies it before desktop startup. A mismatch routes through `SettingsIntegrityAction`: `Confirm` (the default) keeps the confirmation gate that requires the security password before the app continues, otherwise it exits; `AutoRestore` hands startup to `SettingsIntegrityRecoveryService`, which walks the newest `data/backup/*.zip` and/or account cloud backups per the separately configured `SettingsIntegrityRestoreSource` (`Local` / `LocalThenCloud` / `CloudThenLocal`), takes `config/settings.json` from the first supported v3 archive that actually carries a loadable settings payload, copies the current file to `data/backup/integrity/settings_before_restore_*.json`, atomically replaces it, reloads the handler (which refreshes the fingerprint), and restarts the app; only the settings file is touched, and a failed recovery keeps the original file and falls back to the password gate with the failure reason. The fingerprint needs existing security credentials as its anchor, is cleared when protection is turned off, and — because it lives outside the AES-GCM authenticated data — detects accidental or casual edits rather than a determined local attacker who can rewrite both files.
169
170
- OmniTTS cloud speech API keys are credentials too: they live in `data/config/voice/omnitts-keys.json` via `OmniTtsCredentialStore` (app-layer, atomic replace), never in `VoiceSettingsConfig`/`settings.json`, IPC payloads, logs, telemetry, backups, or the Android DocumentsProvider. OmniTTS is voice engine 2 (`OmniTtsSpeechProvider.OmniEngine`), sits beside System SAPI (0) and Edge TTS (1), and is a unified cloud TTS engine whose providers are OpenAI, Gemini, FishAudio, MiMo, and Custom OpenAI-compatible. OpenAI, Gemini, and MiMo voice choices use documented preset sets; Gemini uses native `/v1beta/interactions` synthesis and wraps its 24 kHz PCM response as WAV. MiMo voice-design and voice-clone models use model-specific chat-completions payloads; clone references are WAV files kept privately under `data/config/voice/mimo-voice-reference.wav`, identified by a hash, and excluded from settings, archives, IPC, and logs. Models are fetched from provider APIs or typed manually. The voice settings page shows OmniTTS-specific rows (provider, base URL, key, model, voice, batch cache) only while `VoiceEngine == 2`. `data/config/voice/` is excluded from archives and protected from the Android provider/directory launcher. iOS keeps the voice settings page unregistered (`!OperatingSystem.IsIOS()`); OmniTTS targets Windows, Linux, macOS, and Android.
0 commit comments