fix: refuse to keep a sign-in Linux would store in plain text - #3
Merged
Conversation
secrets.ts opens by promising that when the OS declines to provide encryption we refuse to store the token rather than writing it in the clear, "because a companion that silently downgrades to a plaintext credential on disk is worse than one that asks the officer to sign in again." On a Linux desktop with no keyring running, that is exactly what it did. Electron falls back to a backend it calls `basic_text`, which scrambles with a key hardcoded in Chromium's source and is recoverable by anyone who can read the file. `safeStorage.isEncryptionAvailable()` answers **true** for it, and that answer was the entire check. So the app wrote a working API token — one that uploads attendance for the officer's guild — to disk in effectively plain text, and told them their sign-in had been stored securely. Availability is no longer the whole question. On Linux the selected backend is read too, `basic_text` counts as no store, and the write refuses rather than merely reporting a flag the caller might not consult. A credentials file left by a build that trusted the flag is deleted on the next launch: declining to read it while leaving it there would be the worst of both, since the officer signs in again and the recoverable copy stays forever. The setup panel already had a slot for "sign-in cannot be remembered" and it simply never fired here. It now carries the reason, because "no secure credential store" is true and useless to somebody whose desktop looks perfectly normal and whose missing piece is a daemon they have never had to think about — it names gnome-keyring and KWallet. Windows and macOS are untouched: neither has a backend to choose, and getSelectedStorageBackend is Linux-only and absent on older Electron, so it is guarded by platform and by existence. A test covers both, because dropping either guard would start refusing DPAPI, which works fine. 9 tests. They found an ordering bug on the way: with no encryption available at all, the backend can still report basic_text, and the message was sending people off to start a keyring when that was not the problem. Mutation-checked — skipping the stale-file deletion fails the test that demands it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
secrets.tsopens by promising that when the OS declines to provide encryption, the token is not stored at all — "a companion that silently downgrades to a plaintext credential on disk is worse than one that asks the officer to sign in again."On a Linux desktop with no keyring running, that is exactly what it did.
The gap
Electron falls back to a backend it calls
basic_text, which scrambles with a key hardcoded in Chromium's source — recoverable by anyone who can read the file.safeStorage.isEncryptionAvailable()answers true for it, and that answer was the entire check.So the app wrote a working API token, one that uploads attendance for the officer's guild, to disk in effectively plain text, and told them the sign-in had been stored securely. It was listed in the README as a known gap; this closes it.
What changed
Availability is no longer the whole question. On Linux the selected backend is read as well,
basic_textcounts as no store, andsaveTokenrefuses rather than merely setting a flag a caller might not consult.A credentials file left by a build that trusted the flag is deleted on the next launch. Declining to read it while leaving it on disk is the worst of both: the officer signs in again and the recoverable copy stays there indefinitely.
The setup panel already had a slot for "sign-in cannot be remembered" — it just never fired here. It now carries the reason, because "no secure credential store" is true and useless to somebody whose desktop looks perfectly normal and whose missing piece is a daemon they have never had to think about. It names gnome-keyring and KWallet.
Windows and macOS are untouched. Neither has a backend to choose, and
getSelectedStorageBackendis Linux-only and absent on older Electron — so it is guarded by platform and by existence, with a test for each. Dropping either guard would start refusing DPAPI, which works perfectly well.Verification
9 tests, none of which need a real credential store — they pin the decision made from what one reports. Real store round-trips and writes something other than the bare token;
basic_textrefuses, leaves no file, deletes a stale one, and names the daemon; no-encryption-at-all refuses without inventing a Linux fix; Windows survivesgetSelectedStorageBackendthrowing; macOS is not tripped up by a Linux backend name.They found an ordering bug while being written: with no encryption available at all the backend can still report
basic_text, and the message was sending people off to start a keyring when that was not the problem.Mutation-checked — skipping the stale-file deletion fails the test that demands it.
Full suite 77 pass / 6 skipped, build clean,
SMOKE OK.Note
This does not revoke the exposed token server-side; it only stops the local copy existing. Anyone who has run a Linux build on a keyring-less desktop should revoke that companion token from account settings. Automatic revocation would mean decrypting the old file and making a network call during startup, which is more machinery than the case deserves — but say the word if you want it.
🤖 Generated with Claude Code