feat(desktop-main): add electron-store and safeStorage wrapper for secrets#227
Conversation
…nStoreService Refs #150
There was a problem hiding this comment.
Pull request overview
This PR adds persistent desktop app-state storage for desktop-main and wraps Electron safeStorage for secret encryption, supporting the Electron shell work from issue #150.
Changes:
- Adds
electron-storeas a desktop-main dependency. - Introduces
SafeStorageServicefor Electron keychain-backed string encryption with non-Electron fallback behavior. - Introduces
ElectronStoreServicewith typed app-state schema and encrypted AWS credential helpers, plus unit tests.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
package-lock.json |
Locks electron-store and transitive dependencies. |
app/packages/desktop-main/package.json |
Adds electron-store to desktop-main runtime dependencies. |
app/packages/desktop-main/src/app.module.ts |
Registers the new storage services with Nest DI. |
app/packages/desktop-main/src/services/SafeStorageService.ts |
Adds safeStorage availability, encryption, and decryption wrapper logic. |
app/packages/desktop-main/src/services/SafeStorageService.test.ts |
Covers safeStorage availability, fallback, encryption, decryption, and round-trip behavior. |
app/packages/desktop-main/src/services/ElectronStoreService.ts |
Adds typed app-state store wrapper and encrypted AWS secret helpers. |
app/packages/desktop-main/src/services/ElectronStoreService.test.ts |
Covers Map fallback, mocked Electron store behavior, and secret helper round trips. |
| ).mockReturnValue(mockStore); | ||
|
|
||
| service = new ElectronStoreService(safeStorage); | ||
| vi.clearAllMocks(); |
There was a problem hiding this comment.
Declined. The repo's vitest.config.ts sets restoreMocks: true, which makes Vitest call vi.restoreAllMocks() after every individual test (not just at file boundary). Prototype spies are fully restored after each it() block, so the Electron-branch beforeEach always starts from a clean slate before constructing the next service instance. The cross-describe leakage path the comment describes does not exist under this configuration.
| const buf = Buffer.from(ciphertext, 'base64'); | ||
| return this.decryptString(buf); |
There was a problem hiding this comment.
Declined — already reviewed. Mission Control considered the envelope/prefix approach and chose not to implement it. The invariant ("isAvailable() must return the same value at both write time and read time") is now documented in a @remarks TSDoc block on decrypt(), which calls the output untrusted if the caller violates it. The keychain availability state flip is treated as an operator-level concern, not one the service itself guards against internally.
Closes #150
Summary
electron-store ^11.0.2as the persistent app-state backing store underapp.getPath('userData')/electron-store.jsonSafeStorageServicewrapping Electron'ssafeStorage.encryptString/decryptStringwith graceful non-Electron degradation (plaintext passthrough in CI/tests)ElectronStoreServicewith a typedAppStoreSchema(wizardCompleted, activeCloud, aws.*) and transparent encryption of secret fields (accessKeyId, secretAccessKey) via SafeStorageServiceChanges
Test plan
npm run app:test)Design notes
SafeStorageService.encrypt()/decrypt()gate onisAvailable()(Electron present and OS keychain unlocked), not justreadIsElectron()— prevents throws on Linux without libsecretaws.region/aws.profileare optional inAppStoreSchemaso secrets can be written before the wizard completes region/profile selectionMission log
🤖 Generated via /mission