export type LinuxPasswordStorePreference = | "auto" | "gnome-libsecret" | "kwallet" | "kwallet5" | "kwallet6";export type LinuxPasswordStoreSwitch = Exclude<LinuxPasswordStorePreference, "auto">;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(":") ?? [];}
export type LinuxPasswordStorePreference =
| "auto"
| "gnome-libsecret"
| "kwallet"
| "kwallet5"
| "kwallet6";
export type LinuxPasswordStoreSwitch = Exclude<LinuxPasswordStorePreference, "auto">;
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(":") ?? [];
}