export type LinuxPasswordStorePreference = | "auto" | "gnome-libsecret" | "kwallet" | "kwallet5" | "kwallet6"; export type LinuxPasswordStoreSwitch = Exclude; export const DEFAULT_LINUX_PASSWORD_STORE: LinuxPasswordStorePreference = "auto"; // Chromium matches XDG_CURRENT_DESKTOP values case-sensitively and returns on the first value it // recognizes, so these stay exact literals and are scanned in order. Omitting a real desktop fails // safe: we force gnome-libsecret, which is the backend Chromium selects for all of these anyway. const ELECTRON_LIBSECRET_DESKTOPS = new Set([ "Deepin", "GNOME", "Pantheon", "UKUI", "Unity", "X-Cinnamon", "XFCE", ]); // Chromium selects a KWallet generation for KDE from KDE_SESSION_VERSION, so it needs no help. const ELECTRON_KDE_DESKTOP = "KDE"; // Chromium recognizes LXQt and still selects basic text for it, so it does need a forced backend. const ELECTRON_UNPROTECTED_DESKTOPS = new Set(["LXQt"]); export function normalizeLinuxPasswordStorePreference( value: unknown, ): LinuxPasswordStorePreference { return value === "gnome-libsecret" || value === "kwallet" || value === "kwallet5" || value === "kwallet6" ? value : DEFAULT_LINUX_PASSWORD_STORE; } // Auto mode asks one question: will Electron select a real keyring on its own? If so, stay out of // the way, which is how canonical KDE sessions keep the KWallet generation Chromium picks for them. // Otherwise force gnome-libsecret, because the alternative is basic text, which is barely // encryption at all. Forcing never guesses a KWallet generation; a KDE session that needs a // specific one sets linuxPasswordStore explicitly. export function resolveLinuxPasswordStoreSwitch(input: { readonly preference: LinuxPasswordStorePreference; readonly env: NodeJS.ProcessEnv; }): LinuxPasswordStoreSwitch | null { if (input.preference !== "auto") { return input.preference; } return electronSelectsProtectedBackend(input.env) ? null : "gnome-libsecret"; } // Only an exact XDG_CURRENT_DESKTOP literal proves Electron will protect the session. Chromium can // also reach a real backend through DESKTOP_SESSION and the legacy KDE markers, but those are the // variables a previous session leaves behind, and trusting them is what let stale hints suppress // the forced backend before. Forcing where Chromium would have chosen libsecret is harmless, since // it lands on the same backend. function electronSelectsProtectedBackend(env: NodeJS.ProcessEnv): boolean { for (const name of splitDesktopNameList(env.XDG_CURRENT_DESKTOP)) { const trimmed = name.trim(); if (trimmed.length === 0) { continue; } if (trimmed === ELECTRON_KDE_DESKTOP || ELECTRON_LIBSECRET_DESKTOPS.has(trimmed)) { return true; } if (ELECTRON_UNPROTECTED_DESKTOPS.has(trimmed)) { return false; } } return false; } function splitDesktopNameList(value: string | undefined): string[] { return value?.split(":") ?? []; }