Skip to content

feat(profiles): add profile wizard, migration, and activation flow#448

Open
0r0b0r011 wants to merge 5 commits into
fathah:mainfrom
0r0b0r011:split/profile-wizard
Open

feat(profiles): add profile wizard, migration, and activation flow#448
0r0b0r011 wants to merge 5 commits into
fathah:mainfrom
0r0b0r011:split/profile-wizard

Conversation

@0r0b0r011

Copy link
Copy Markdown

Summary

Adds guided profile creation and migration on top of the encrypted vault (#446):

  • Wizard: Multi-step profile setup with templates, toolsets, secrets, and validation
  • Migration: Detects legacy plaintext .env files and offers one-time import into vault
  • Activation: Profile switch writes vault secrets to .env, starts/restarts gateway with rollback
  • UI: Profile wizard overlay, migration modal, Ctrl/Cmd+P profile switcher, gateway status in sidebar
  • Tests: tests/wizard-profile-write.test.ts covers filesystem write safety

Depends on #446 (vault). This branch is stacked on split/vault-credentials. Merge #446 first, then rebase this PR onto main before final merge.

Part of the split from #438.

Security notes for reviewers

  • Filesystem writes: Wizard creates profile dirs, soul, config, and .env — review rollback on failure
  • Vault integration: Activation/deactivation reads/writes secrets via vault service
  • Migration: Imports plaintext env keys into vault — confirm one-time flow and no duplicate imports
  • Gateway lifecycle: waitForGatewayReady polls API health after restart during activation

Test plan

  • npm run typecheck
  • npm test -- tests/wizard-profile-write.test.ts tests/vault.test.ts tests/ipc-handlers.test.ts tests/preload-api-surface.test.ts
  • npm run build
  • Manual: run migration wizard on profile with legacy .env
  • Manual: create profile via wizard, activate, verify gateway connects
  • Manual: Ctrl/Cmd+P profile switcher

Made with Cursor

0r0b0r011 and others added 2 commits May 29, 2026 13:26
Store profile-scoped secrets in an encrypted SQLite vault with IPC
handlers, preload namespace, and a Vault screen for credential CRUD.

Co-authored-by: Cursor <cursoragent@cursor.com>
Multi-step profile creation with templates, vault-backed secret migration,
gateway activation with rollback, and UI overlays for wizard/migration/switcher.

Co-authored-by: Cursor <cursoragent@cursor.com>
Ensure profile switcher, chat-with, and delete flows wait for
handleSelectProfile to finish gateway activation before continuing.

Co-authored-by: Cursor <cursoragent@cursor.com>

@pmos69 pmos69 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for splitting this out and for making the dependency on #446 explicit. I did a static review only because this PR wires credential storage, plaintext env migration, profile activation, and gateway restarts into the app. I do not think this is safe to merge yet.

This PR is stacked on #446, so the vault concerns from that PR still apply here. More importantly, #448 turns several previously dormant issues into live profile-switching behavior.

Blocking findings

1. Migration leaves plaintext secrets behind indefinitely

profile-migrate-secrets copies every migrated .env to ${envFile}.backup, imports the values, then replaces .env with comments that explicitly point to the backup. That means after the user chooses the advertised “migrate plaintext secrets into the encrypted vault” path, the same secrets still remain in plaintext at a predictable path.

Relevant code:

  • src/main/ipc/profile-handlers.ts:126-130
  • src/main/ipc/profile-handlers.ts:146-152

This defeats the main purpose of the vault migration. At minimum, migration needs a verified import/readback path and then removal of plaintext copies, or it should avoid creating a plaintext backup in the first place. If a recoverable backup is required, it needs an explicit user-facing export/save flow, not an automatic forever file beside the original .env.

2. Env key migration is lossy and can break gateway auth

The migration stores only a derived provider id, then activation reconstructs an env var from that provider. These transforms are not inverses.

Example: API_SERVER_KEY

  1. parseEnvFile() accepts API_SERVER_KEY.
  2. envKeyToProvider("API_SERVER_KEY") stores provider api-server.
  3. resolveEnvKey("api-server") later reconstructs API_SERVER_API_KEY.
  4. The gateway still expects API_SERVER_KEY, so the migrated/activated profile can lose gateway auth.

Relevant code:

  • src/main/vault/service.ts:87-98
  • src/main/vault/service.ts:329-371

This also affects custom provider env vars and other non-standard keys. The vault row needs to preserve the original env var name, or migration must otherwise be round-trip-lossless.

3. Profile activation now makes the inherited .env clobber live

activateProfileWithRollback() calls vaultActivate(profile), and vaultActivate() rewrites .env with only the decrypted vault entries. Any non-vault .env keys disappear: gateway keys, HERMES_INFERENCE_PROVIDER, custom provider settings, platform-specific variables, etc.

Relevant code:

  • src/main/profiles/wizard.ts:214
  • src/main/vault/service.ts:237-255

In #446 this was concerning but mostly dormant. In this PR it runs during real profile switching, so it becomes a data-loss / configuration-corruption path. Activation should merge managed secret keys into the existing .env, preserving unrelated keys, and it should properly quote/escape values.

4. Activation backups create more plaintext secret sprawl

Every activation copies .env and auth.json into a timestamped desktop/activation-backup/... directory and never removes that backup on success.

Relevant code:

  • src/main/profiles/wizard.ts:202-212

This means regular profile switching can accumulate plaintext API keys and OAuth tokens indefinitely. If rollback backups are needed, they should be scoped to the transaction, cleaned up on success, and avoid copying unmanaged token files unless strictly necessary.

5. Failed activation can leave the active profile/gateway state inconsistent

The renderer first calls setActiveProfile(name), then calls profileWizard.activate(name). The activation path also calls setActiveProfile(profile) before waiting for gateway readiness. If gateway startup fails, the catch block restores some files but does not restore the previous active profile, remove newly-created files, or restart the gateway back onto the restored configuration.

Relevant code:

  • src/renderer/src/screens/Layout/Layout.tsx:236-250
  • src/main/profiles/wizard.ts:214-235

Rollback needs to cover the full transaction: files, active profile state, and gateway process state.

6. New profile/vault IPC is not enforced in main-process remote mode

The UI hides some local-only surfaces in remote mode, but these handlers are still registered and callable from renderer code. The new profile activation/migration paths can write local profile files, mutate the local vault, and start/restart the local gateway even when the app is connected to SSH/remote mode.

Relevant code:

  • src/main/ipc/profile-handlers.ts:25-143
  • src/main/ipc/vault-handlers.ts:15-66
  • src/main/index.ts:1550-1551

This needs the same main-process enforcement pattern used by other local-only features. Renderer gating is not a security or correctness boundary.

7. Migration detection/import is not actually one-time per profile

profile-detect-migration returns [] as soon as the vault has any secrets, regardless of which profile they belong to. So migrating profile A can prevent profile B from ever being offered migration. Conversely, migratePlaintextEnv() always inserts fresh rows and has no dedupe guard, so direct/repeated calls can duplicate imported secrets.

Relevant code:

  • src/main/ipc/profile-handlers.ts:87-111
  • src/main/vault/service.ts:359-371

The migration state needs to be per-profile/per-env-key, and imports should be idempotent.

8. Archive-on-delete can preserve plaintext secrets

profile-delete with archive copies the whole profile home before removing vault secrets. If the profile contains .env, .env.backup, or other plaintext files, those are retained under archived-profiles.

Relevant code:

  • src/main/ipc/profile-handlers.ts:63-79

Given this PR introduces migration backups and activation backups, archive behavior needs to be reviewed as part of the same secret-retention model.

Inherited #446 blockers still apply here

Because this branch includes the vault commit from #446, the #446 issues are also present here: vault IPC remote-mode bypass, overly broad renderer authority, raw vault export, locked/password fallback UI path, vault DB permissions, key format ambiguity between safeStorage/password modes, non-atomic key rotation, and insufficient tests around security boundaries.

Minimum bar before this can be reconsidered

  • Land/fix #446 first, then rebase this PR.
  • Store original env var names or otherwise make migration/activation round-trip-lossless.
  • Preserve unrelated .env keys during activation; do not overwrite the whole file with only vault entries.
  • Do not leave plaintext .env.backup, activation backups, or archived plaintext secrets behind silently.
  • Make migration detection/import per-profile and idempotent.
  • Add main-process remote-mode guards for all new vault/profile IPC handlers.
  • Roll back active profile and gateway process state on activation failure.
  • Add tests for lossy env-key round trips (API_SERVER_KEY specifically), .env preservation, plaintext-backup cleanup, per-profile migration, duplicate migration, failed activation rollback, and remote-mode IPC rejection.

The wizard structure and the activation rollback shape are good directions, but the current integration can both leak plaintext secrets and corrupt a working Hermes configuration. Requesting changes.

0r0b0r011 and others added 2 commits May 30, 2026 22:35
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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.

2 participants