Skip to content

feat(desktop-main): add electron-store and safeStorage wrapper for secrets#227

Merged
CoderCoco merged 9 commits into
mainfrom
claude/issue-150-feat-desktop-main-electron-store-for-app-state-saf
May 31, 2026
Merged

feat(desktop-main): add electron-store and safeStorage wrapper for secrets#227
CoderCoco merged 9 commits into
mainfrom
claude/issue-150-feat-desktop-main-electron-store-for-app-state-saf

Conversation

@CoderCoco
Copy link
Copy Markdown
Owner

Closes #150

Summary

  • Adds electron-store ^11.0.2 as the persistent app-state backing store under app.getPath('userData')/electron-store.json
  • Introduces SafeStorageService wrapping Electron's safeStorage.encryptString/decryptString with graceful non-Electron degradation (plaintext passthrough in CI/tests)
  • Introduces ElectronStoreService with a typed AppStoreSchema (wizardCompleted, activeCloud, aws.*) and transparent encryption of secret fields (accessKeyId, secretAccessKey) via SafeStorageService

Changes

app/packages/desktop-main/package.json             |   1 +   electron-store ^11.0.2 added
app/packages/desktop-main/src/app.module.ts        |   4 +   both services registered as providers
src/services/ElectronStoreService.ts               | 161 +++ new service + AppStoreSchema
src/services/SafeStorageService.ts                 | 134 +++ new service
src/services/ElectronStoreService.test.ts          | 233 +++ 12 unit tests
src/services/SafeStorageService.test.ts            | 209 +++ 16 unit tests (inc. keychain-unavailable path)

Test plan

  • 553 unit tests pass (npm run app:test)
  • Lint passes (0 errors)
  • TypeScript clean on new files
  • Manual: app state (wizardCompleted, activeCloud, aws.region/profile) survives an Electron restart
  • Manual: accessKeyId/secretAccessKey unreadable without OS keychain (base64 blob on disk)
  • Manual: graceful degradation on Linux CI runner without libsecret (plaintext passthrough with warning log)

Design notes

  • SafeStorageService.encrypt()/decrypt() gate on isAvailable() (Electron present and OS keychain unlocked), not just readIsElectron() — prevents throws on Linux without libsecret
  • aws.region/aws.profile are optional in AppStoreSchema so secrets can be written before the wizard completes region/profile selection
  • Outside Electron both services degrade to an in-memory Map; the public API is identical so callers need no branching
Mission log
pre-launch    ✓  6 tasks planned
liftoff       ✓  6/6 tasks passed (Apollo→Faraday)
systems-check ✓  3 findings fixed (Glenn, Hadfield, Irwin)

🤖 Generated via /mission

Copilot AI review requested due to automatic review settings May 31, 2026 18:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-store as a desktop-main dependency.
  • Introduces SafeStorageService for Electron keychain-backed string encryption with non-Electron fallback behavior.
  • Introduces ElectronStoreService with 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();
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +79 to +80
const buf = Buffer.from(ciphertext, 'base64');
return this.decryptString(buf);
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@CoderCoco CoderCoco merged commit f0a2aa0 into main May 31, 2026
7 of 9 checks passed
@CoderCoco CoderCoco deleted the claude/issue-150-feat-desktop-main-electron-store-for-app-state-saf branch May 31, 2026 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(desktop-main): electron-store for app state, safeStorage wrapper for secrets

2 participants