{"record":{"id":"c1cba50e77f2a496","repo":"NousResearch/hermes-agent","slug":"secure-token-storage-is-unavailable-no-os-keyring","errorCode":null,"errorMessage":"Secure token storage is unavailable (no OS keyring service was found), so Hermes Desktop cannot save remote gateway tokens. Either enable an OS keyring (e.g. GNOME Keyring or KWallet providing org.freedesktop.secrets) and try again, confirm the plain-text storage option when prompted in Settings → Gateway, or set HERMES_DESKTOP_REMOTE_URL and HERMES_DESKTOP_REMOTE_TOKEN in your environment.","messagePattern":"Secure token storage is unavailable \\(no OS keyring service was found\\), so Hermes Desktop cannot save remote gateway tokens\\. Either enable an OS keyring \\(e\\.g\\. GNOME Keyring or KWallet providing org\\.freedesktop\\.secrets\\) and try again, confirm the plain-text storage option when prompted in Settings → Gateway, or set HERMES_DESKTOP_REMOTE_URL and HERMES_DESKTOP_REMOTE_TOKEN in your environment\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/hardening.ts","lineNumber":192,"sourceCode":"  const allowPlainText = options?.allowPlainText === true\n\n  let encryptionAvailable = false\n\n  try {\n    encryptionAvailable = Boolean(safeStorageApi?.isEncryptionAvailable?.())\n  } catch {\n    encryptionAvailable = false\n  }\n\n  if (!encryptionAvailable) {\n    // Only downgrade to plain text when the user has explicitly opted in;\n    // decryptDesktopSecret returns the raw value for any non-'safeStorage'\n    // encoding, so this round-trips without any decrypt-side change.\n    if (allowPlainText) {\n      return { encoding: 'plain', value: raw }\n    }\n\n    throw new Error(\n      'Secure token storage is unavailable (no OS keyring service was found), so Hermes Desktop cannot save remote gateway tokens. ' +\n        'Either enable an OS keyring (e.g. GNOME Keyring or KWallet providing org.freedesktop.secrets) and try again, ' +\n        'confirm the plain-text storage option when prompted in Settings → Gateway, ' +\n        'or set HERMES_DESKTOP_REMOTE_URL and HERMES_DESKTOP_REMOTE_TOKEN in your environment.'\n    )\n  }\n\n  try {\n    return {\n      encoding: SAFE_STORAGE_ENCODING,\n      value: safeStorageApi.encryptString(raw).toString('base64')\n    }\n  } catch (error) {\n    const detail = error instanceof Error && error.message ? ` (${error.message})` : ''\n    throw new Error(\n      `Failed to encrypt the remote gateway token for secure storage${detail}. ` +\n        'Set HERMES_DESKTOP_REMOTE_URL and HERMES_DESKTOP_REMOTE_TOKEN in your environment as a fallback.'\n    )","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/hardening.ts#L174-L210","documentation":"Thrown while persisting a remote gateway token when Electron's safeStorage backend reports no OS keyring service (typical on Linux without org.freedesktop.secrets) and the user has not opted into plain-text storage. The function only downgrades to { encoding: 'plain' } when the allowPlainText flag is set; otherwise it refuses to save the secret in the clear.","triggerScenarios":"Saving a token in Settings → Gateway on Linux running a WM without GNOME Keyring/KWallet (Hyprland, Sway, some minimal setups); headless-ish or containerized desktop sessions where the secrets service D-Bus name is absent; safeStorage.isEncryptionAvailable() returning false for any reason and allowPlainText not passed.","commonSituations":"Tiling-WM users; distros that don't auto-start a keyring; users who dismissed the plain-text confirmation prompt and retried the save.","solutions":["Install/enable a keyring providing org.freedesktop.secrets (gnome-keyring or kwallet) and restart the session, then retry.","Confirm the plain-text storage option when prompted in Settings → Gateway (explicit opt-in).","Alternatively set HERMES_DESKTOP_REMOTE_URL and HERMES_DESKTOP_REMOTE_TOKEN in the environment so no token needs to be stored."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"const { safeStorage } = require('electron')\nfunction canStoreSecretsSecurely() {\n  return safeStorage.isEncryptionAvailable()\n}","typeGuard":null,"tryCatchPattern":"try {\n  await saveRemoteToken(token, { allowPlainText: false })\n} catch (e) {\n  if (/Secure token storage is unavailable/.test(e.message)) {\n    promptEnableKeyringOrPlainText()\n  } else throw e\n}","preventionTips":["Run a keyring daemon providing org.freedesktop.secrets on Linux","Decide the plain-text policy up front in Settings","Keep HERMES_DESKTOP_REMOTE_URL/TOKEN as an env fallback for headless setups"],"tags":["desktop","keyring","safe-storage","linux","secrets","electron"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}