docs(auth): the device side's rest of the full reading (ADR 0017)

ADR 0017's full-reading part says what the device's fixes did: the
clipboard, plain HTTP refused but on this machine, sheets never left busy,
what Save leaves, secrets out of debug logs, keyboards, screenshots and
backups, memory, and the real cryptography against the register's checks.
halkyon_app's README says what an app must add (its activity's screen
guard), Learn's AGENTS.md the HTTPS rule for instances.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-21 15:12:43 +02:00
commit a72b96d628
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -494,6 +494,47 @@ refused but on this machine; the phrase is saved, never copied. Server side:
every endpoint's, are `instanceUrl`. Each refusal has a test, which fails with the
binding taken out on both sides.
The device side's rest (21 Oct 2026):
- **The device's medium 2, secrets on the clipboard.** The recovery phrase is saved as a
file or written down, never copied (clipboard histories and cloud clipboard sync would
keep it); recovery codes and an authenticator's key are copied and the clipboard
emptied 60 seconds later if it still holds them (`copySecret`), and they are no longer
selectable, as the system's own copy wouldn't empty it.
- **`http://` instances**, refused but on this machine (`localhost`, `127.0.0.0/8`,
`::1`; `Instance.isSecure`): an address over plain HTTP elsewhere is refused before
anything is sent there; one kept from before is never opened, the app saying why and
offering another instance or removing it, never dropping it silently
(`InstanceChoice.allows`, `leave`). Development builds still use their own instance
over HTTP (an emulator reaches the developer's machine as 10.0.2.2); releases never.
- **Low 1, sheets left busy.** halkyon_keys checks the length of what comes from
elsewhere before libsodium sees it (`KeyRefusal.malformed`: a salt, a wrapping, a
sealed key's box, a sealed payload), and the device's cryptography and every password
cubit turn anything unexpected into the general failure, logged by its type alone, so
a sheet always closes again.
- **Low 2, what Save leaves.** On iOS the copy file_picker writes into the temporary
folder for the system's picker goes once it is done; on the desktop a saved file is the
own user's alone (0600, set before anything is written; Windows keeps its folder's
rights).
- **Low 3, secrets in debug logs.** The password states and `AuthenticatorSetup` say
only where they stand, never an authenticator's secret, the recovery codes or the
address; Learn's bloc observer's test holds what it logs to that.
- **Low 5, hardening.** Keyboards learn nothing typed into a password, a code or the
phrase (`enableIMEPersonalizedLearning: false`); the phrase, recovery codes and an
authenticator's QR code and key show in a `SecretScreen`, kept out of screenshots, screen
recordings and recents on Android (`FLAG_SECURE`, each app's `ScreenGuard.kt`) and
covered while the app isn't in front on iOS (not checked on a device); the account's
keys stay on this device on iOS and macOS (`first_unlock_this_device`, never iCloud
Keychain); only an `otpauth:` URI is shown as a QR code or opened.
- **Low 6, memory.** The account's and data keys go from guarded memory straight into
the buffer libsodium encrypts, no copy left on the way; halkyon_keys' README says
exactly what is wiped and what Dart can't wipe (x as a `BigInt`, `String`s, what
isolates copy, the keyring's base64).
- **Low 7, the device's real cryptography against the register's checks.**
`register_interop_test.dart` (halkyon_app) holds what libsodium makes on the device to
`isPasswordSetup`, `srpVerify` and M2, `isNewPassword` and the phrase's proof key, bound
to the instance and the purpose.
### The rules as they stand (21 Oct 2026)
Who changes what of an account, with which proofs. "Password" is SRP's proof bound to