docs(auth): what the third reading found, and Proton's rule (ADR 0017)

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 10:45:46 +02:00
commit 1ada2e27f6
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -338,6 +338,57 @@ these; all but the ninth were fixed (the developer's choice):
unavailable**, which the app read as "no password sign-in here": it has its own
refusal now (`passwordChanged`), saying to sign in again with the new password.
### Third reading (21 Oct 2026)
A third independent reading, of the re-reading's fixes (`1383640..a45842f`), found its
items 1, 2, 4, 6, 7 and 10 fixed and 5 and 8 holding but for the points below. The first
finding was a regression of item 3, so rather than count once more, the developer chose
Proton's rule (21 Oct 2026):
- **A session alone never gets a code checked.** Every signed-in change of how an
account is secured takes the owner's proof (`OwnerProof`): the password's SRP proof
for a change's challenge (`password.beginChange`, the person checked) and then a TOTP
or recovery code. That is adding an authenticator once one is confirmed, removing a
confirmed one, new recovery codes, a new phrase, a new password and deleting. Only the
first authenticator (none confirmed, after a reset carried out too) is confirmed with
its own code alone, and removing one never confirmed takes nothing. A proof given where
none is taken is refused, and a confirmed authenticator's code is never checked again
for a session (confirming it again is refused as unknown), so no success passes a
proof unchecked. `wrongExistingCode` goes: a wrong code from another authenticator is
a wrong proof.
- **A session's proofs count for it alone** (`asOwnerProofTry`, table
`session_code_failures`, where the session is, on a member too): each counted before
it is checked, cleared only by one accepted, the tenth wrong in a row ending the
session. They never count toward the account's waits nor wait for them, and a new
password forgets those waits.
The findings, and what was done:
1. **A stolen session guessed codes without end** (high): nine wrong codes for new
recovery codes, then removing an unconfirmed authenticator of its own, a success that
cleared the count, and again. Closed by the rule: no code is checked for a session
without the password first, and only a proof accepted clears the count.
2. **Whoever knew the password held a signed-in owner out of changing it**: ten wrong
codes at sign-in made the account's codes wait, and the owner's change waited too.
Signed-in proofs no longer wait for the account's waits, and the change forgets them.
3. **A stolen session spent the ten changes an account begins an hour**, holding its
owner out of changing the password, a new phrase and deleting. Beginning counts
nothing against the account: at most 30 an hour per session, a bound on the work one
session asks for that holds back no other; only failed proofs count, per session.
4. **Deleting, with the authenticators' reset taken effect but not carried out**, was
offered a code field (the app read "an authenticator was confirmed") which the
instance refused (it read "no second factor works"). The instance takes a TOTP or
recovery code wherever an authenticator is confirmed, a code by mail where no second
factor works, and tells the app which (`SignInWays.secondFactorWorks`).
5. **A device that didn't know the account's address couldn't change the password, set a
new phrase or delete** (`addressUnknown`). Signed-in calls take no address any more:
nothing there mails, the account's waits being the sign-in's. Deleting by mail asks
for the address where the device doesn't know it, as the code goes there; the
instance takes the code only for the account's own address.
6. **The code mail's subject hid its purpose**, which a notification shows alone. The
subject names it ("K7QX-M2PD: your Halkyon Learn code to delete your account"), and the
sign-up mail says it also gives a password to an account with that address and none.
## Consequences
- People keep a password, an authenticator app and a recovery phrase: more to keep than a