secrets
Source: secrets.js:21
Encrypted secrets store for OAuth tokens and API keys. Secrets NEVER go in settings.json; they live here, encrypted at rest via Electron's safeStorage, whose master key is protected by the OS credential store (macOS Keychain, Windows Credential Manager / DPAPI, Linux libsecret / kwallet when available).
Only the ciphertext (base64) is held in a file alongside settings, at app.getPath('userData')/secrets.json; safeStorage holds the key. So the file by itself is useless to anyone who copies it off the disk — defending against offline file theft and backup leakage. It does NOT defend against code already running as this user (that code can ask safeStorage to decrypt); that's the standard tradeoff of OS-backed app secret storage.
Security posture: main process only. The renderer is deliberately given NO way to read or write plaintext secrets (see main.js: only availability/has/delete are exposed over IPC). In the real OAuth flow (D8/D9) the token is obtained and stored entirely in the main process, so plaintext never crosses into the renderer.
Instance Methods
filePath(): string
Absolute path of secrets.json in the Electron userData directory.
Returns
string— File path.
isAvailable(): boolean
Whether safeStorage can actually encrypt. On Linux with no keyring backend this is false; callers must check before set() and degrade gracefully (e.g. "secure storage unavailable — can't connect this account") rather than storing plaintext.
Returns
boolean— True when encryption is available.
load(): Object.<string, string>
Read the ciphertext map from disk once, then serve from memory. Missing file = no secrets yet; an unreadable/corrupt file is logged and treated as empty so a bad file can't brick launch (the secrets it held are lost, which is acceptable — the user simply re-connects the affected account).
Returns
Object.<string, string>— The ciphertext map.
persist(data: Object.<string, string>): void
Atomic write with owner-only permissions (0o600). Even though the contents are already ciphertext, restricting the file mode is cheap defense-in-depth. Temp-file + rename means a crash mid-write can't leave a half-written secrets.json.
Parameters
data(Object.<string, string>) — Ciphertext map to persist.
Returns
void
setSecret(key: string, value: string): void
Encrypt value and persist it under key. Throws if secure storage isn't available — falling back to plaintext is refused.
Parameters
key(string) — Secret name, e.g. "frameio.accessToken".value(string) — Plaintext secret to encrypt and store.
Returns
void
Throws
Error— When secure storage is unavailable orvalueis not a string.
getSecret(key: string): string
Decrypt and return the secret, or null if no secret is stored under key. Throws only when a blob exists but can't be decrypted (storage went unavailable, or key mismatch). Main-process use only — never wire this to the renderer.
Parameters
key(string) — Secret name.
Returns
string— Plaintext secret, or null when absent.
Throws
Error— When a blob exists but secure storage can't decrypt it.
hasSecret(key: string): boolean
Whether a secret is stored under key. Cheap boolean — no decryption, so it works even when safeStorage is unavailable. This is what the renderer uses to show connected state.
Parameters
key(string) — Secret name.
Returns
boolean— True when a blob exists forkey.
deleteSecret(key: string): boolean
Remove a secret (account disconnect). No-op if absent.
Parameters
key(string) — Secret name.
Returns
boolean— True if something was removed.
listKeys(): Array.<string>
The keys currently held (no values). Handy for diagnostics / "which accounts are linked".
Returns
Array.<string>— Stored secret names.
Instance Fields
cache: Object.<string, string>
In-memory map of key → base64 ciphertext — encrypted blobs, never plaintext. Null until first load.