FEAT: Multi-profile vaults with isolated credentials and per-profile master passwords #19
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/vault/profiles"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Introduces profiles: each profile is a separate vault file with its own
Curve25519 keypair, its own Argon2id salt and its own master password. Every
command operates on the active profile only, so unrelated secrets - work and
personal, say - stay cryptographically isolated on the same machine.
Existing vaults are migrated automatically and losslessly on first run.
Functional Changes
New commands
kosh use [profile]- switch the active profile. With no arguments, opens aninteractive picker (type to filter, enter to select, esc to cancel). Fails if
the named profile does not exist; it does not create profiles. The choice is
persisted to config.
kosh profile list [filter]- table of profiles and which one is active.Reads filenames only, so it opens no vault and requires no master password.
kosh profile create <name>- creates the profile, switches to it, andinitializes its vault with a master password of its own.
kosh initis notneeded afterwards.
kosh profile delete <name>- deletes a profile and every credential in it.Guarded four ways: the active profile cannot be deleted, the target profile's
own master password must be verified, a confirmation phrase must be typed out
in full, and the vault file is overwritten with random bytes and synced to
disk before it is unlinked.
kosh copy <id> <profile>- copies a credential into another profile's vault.The secret is decrypted with the active profile's password and re-encrypted
for the target under a fresh ephemeral keypair and nonce. The original is left
untouched.
On-disk layout and migration
~/.kosh/kosh.dbbecomes~/.kosh/profiles/<name>.db, with the active profilerecorded in
~/.kosh/config.json(0600;~/.koshstays0700).Migration runs on every startup and acts once: if
profiles/default.dbalreadyexists it exits early, otherwise a legacy
kosh.dbis renamed into place asthe
defaultprofile. Nothing is re-encrypted and the master password isunchanged. The early exit also means a stray
kosh.dbcan never clobber anexisting default profile.
Existing commands
kosh init- now scoped to the active profile, idempotent, and reportsvault already initializedrather than touching existing data.kosh list- auto-sized table instead of fixed-width truncation; addsAccess Count, and renders last-used/last-updated as relative times.kosh add/kosh delete/kosh update- destructive paths now show acaution block and require a typed confirmation phrase.
kosh generate- flag state moved off package globals onto a per-commandstruct.
Longdescription and workedExampleblock.Internal Changes
NewCmdX(ctx *app.Context)constructorsregistered in
cmd/root.go, replacing package-level globals andinit()sideeffects. The store is opened in
PersistentPreRun, closed inPersistentPostRun.core.VaultServiceandcore.ProfileServiceare nowinterfaces (
KoshVault,KoshProfile), so commands can be tested with fakes.schema_migrationstable and an ordered migrationlist applied transactionally on store init. DDL moved out of
InitializeVault, which now only inserts the vault row.log/slog. Lower layers returnerrors without logging them;
Execute()logs once and prints once.constants.ErrCredentialNotFoundinstead of leaking
sql.ErrNoRows; vault/store errors are wrapped with%w.internal/uisurface:output.go(profile prefix, glyphs,Caution),table.go(zero-dependency auto-sized table),time.go(RelativeTime).internal/config,internal/app,internal/model/profile.go,internal/crypto/file.go,cmd/profile/.Security
DecryptCredentialreturns[]byterather thanstring, so plaintext is notcopied into an immutable value.
Credential,CredentialDataandCredentialSummaryimplementslog.LogValueand redact secrets, nonces and ephemeral keys, so debug outputcannot print ciphertext by accident.
crypto/subtleconstant-timecomparison.
on top of SQLite's
secure_delete=ON.Bug Fixes
Ctrl+Cduring a password prompt no longer leaves the terminal with echodisabled - terminal state is restored and the process exits
130.kosh generate --lowerwas documented as "include uppercase letters".⚠ Breaking Change
Debug logging no longer requires a rebuild. The
logger.BuildModeldflag is removed from.goreleaser.yaml; setKOSH_DEBUGto a truthy value instead. Logs are structured and go to stderr.
Added
Changed
kosh profile createnow first initializes vault and then switches to it. Switch profile only when persisted.WIP: FEAT: Multi-profile vaults with isolated credentials and per-profile master passwordsto FEAT: Multi-profile vaults with isolated credentials and per-profile master passwords