Skip to content

fix: refuse to keep a sign-in Linux would store in plain text - #3

Merged
FriskyyDev merged 1 commit into
mainfrom
fix/linux-plaintext-token
Aug 4, 2026
Merged

fix: refuse to keep a sign-in Linux would store in plain text#3
FriskyyDev merged 1 commit into
mainfrom
fix/linux-plaintext-token

Conversation

@FriskyyDev

Copy link
Copy Markdown
Owner

secrets.ts opens 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_text counts as no store, and saveToken refuses 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 getSelectedStorageBackend is 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_text refuses, leaves no file, deletes a stale one, and names the daemon; no-encryption-at-all refuses without inventing a Linux fix; Windows survives getSelectedStorageBackend throwing; 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

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>
@FriskyyDev
FriskyyDev merged commit 797797b into main Aug 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant