Skip to content

Commit 386a88d

Browse files
fix(desktop): refuse v10 fallback ciphertext from the async Linux keyring
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent a0dc6d6 commit 386a88d

1 file changed

Lines changed: 20 additions & 4 deletions

File tree

‎apps/desktop/src/electron/ElectronSafeStorage.ts‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ export class ElectronSafeStorageDecryptError extends Schema.TaggedError<Electron
4444
}
4545
}
4646

47+
// Chromium's v10 Linux prefix marks ciphertext under the fixed "peanuts" key. The async
48+
// API falls back to it when the keyring provider fails or is cancelled, while still
49+
// reporting the keyring backend, so it would store credentials effectively in plaintext.
50+
const LINUX_FALLBACK_CIPHERTEXT_PREFIX = Buffer.from("v10");
51+
4752
export const ElectronSafeStorageError = Schema.Union([
4853
ElectronSafeStorageAvailabilityError,
4954
ElectronSafeStorageEncryptError,
@@ -96,10 +101,21 @@ export const make = Effect.gen(function* () {
96101
}),
97102
encryptString: (value) =>
98103
Effect.tryPromise({
99-
try: async () =>
100-
(await isAsyncEncryptionAvailable())
101-
? Electron.safeStorage.encryptStringAsync(value)
102-
: Electron.safeStorage.encryptString(value),
104+
try: async () => {
105+
if (!(await isAsyncEncryptionAvailable())) {
106+
return Electron.safeStorage.encryptString(value);
107+
}
108+
const encrypted = await Electron.safeStorage.encryptStringAsync(value);
109+
if (
110+
platform === "linux" &&
111+
encrypted
112+
.subarray(0, LINUX_FALLBACK_CIPHERTEXT_PREFIX.length)
113+
.equals(LINUX_FALLBACK_CIPHERTEXT_PREFIX)
114+
) {
115+
throw new Error("The keyring fell back to an insecure fixed encryption key.");
116+
}
117+
return encrypted;
118+
},
103119
catch: (cause) => new ElectronSafeStorageEncryptError({ cause }),
104120
}),
105121
decryptString: (value) =>

0 commit comments

Comments
 (0)