feat(ui-react): add Secure Vault for encrypted SSH private key storage#5903
feat(ui-react): add Secure Vault for encrypted SSH private key storage#5903gustavosbarreto merged 14 commits intomasterfrom
Conversation
06e4ad4 to
ab6b7e7
Compare
ab6b7e7 to
78bbe9f
Compare
|
/review |
|
Claude finished @gustavosbarreto's task in 8m 48s —— View job Code Review CompleteReviewed 39 files across code quality, security, testing, TypeScript/React patterns, and architecture. 14 inline issues posted (1 high, 9 medium, 4 low). HighlightsSecurity — The crypto implementation is sound: AES-256-GCM with PBKDF2-SHA256 at 600k iterations, fresh random IVs, session key in module scope (not React state or localStorage), vault locked on logout. Challenge-response SSH signing correctly keeps the private key client-side. One gap: Architecture — The Performance — Six components use Additional notesTesting gaps — 311 tests cover the store, crypto, SSH utils, backend, hook, and some UI components well. However, four vault UI components have zero test coverage: Token expiry — The If you've addressed the feedback and want a new review, tag |
78bbe9f to
30ee484
Compare
9c89a8b to
807c09a
Compare
|
/review |
|
Claude finished @gustavosbarreto's task in 6m 31s —— View job Code Review CompleteReviewed 39 files across code quality, security, testing, TypeScript/React patterns, and architecture — no issues found. All fixes from the previous five review rounds were verified as correctly applied. The code looks good as-is. If you push additional changes and want a new review, tag |
8e3b35a to
df7b68c
Compare
Add sshpk and node-rsa for SSH private key parsing, fingerprint extraction, and challenge-response signing. Add vite-plugin-node-polyfills to provide Node.js globals (process, Buffer) required by these libraries in the browser environment.
Define VaultMeta, VaultData, VaultKeyEntry, VaultStatus, and LegacyPrivateKey types used across the Secure Vault feature.
Implement AES-256-GCM encryption/decryption and PBKDF2-SHA256 key derivation using the Web Crypto API. Includes session key management (module-scoped CryptoKey), vault metadata creation, and password verification via encrypted verifier pattern.
…tation Define IVaultBackend interface for storage abstraction, implement LocalVaultBackend using localStorage for community/self-hosted edition, and add singleton factory for backend instantiation. The adapter pattern allows future server-side storage without changing vault logic.
dc1b810 to
cbfb45e
Compare
Add private key validation with encrypted key detection (KeyEncryptedError), MD5 fingerprint extraction, and challenge-response signature generation using sshpk and node-rsa. Supports RSA (pkcs1-sha1) and ED25519/ECDSA (sha512) key types.
Implement vault state management with full lifecycle: initialize, unlock, lock, and key CRUD operations (add, update, remove). Includes duplicate key prevention (name and data uniqueness), master password change, vault reset, and legacy key migration from the old Vue UI format.
Add VaultSetupDialog for first-time master password creation, VaultUnlockDialog for vault unlock with autocomplete suppression, VaultLockedBanner for locked state indication, and VaultSettingsSection with master password change, lock, and reset vault functionality.
Add the main Secure Vault page with three states: uninitialized (onboarding), locked (banner), and unlocked (key table with search). Includes KeyDrawer for add/edit with automatic encrypted key detection and passphrase validation, KeyDeleteDialog for key removal, and duplicate key prevention with per-field error messages.
Clear the vault session key from memory when the user logs out, covering explicit logout, token expiration, and 401 responses.
Extract the duplicated file-input logic (FileReader, 512KB limit, drag-and-drop, paste interception, file/text mode toggle) into useKeyFileInput hook and KeyFileInput component. Refactor public-keys KeyDataInput to use them.
Replace the inline file/text input block with the shared KeyFileInput component, removing duplicated file-input logic.
Cover vault crypto utilities, localStorage backend, SSH key utils, vault Zustand store, KeyFileInput hook and component, and secure-vault page components.
cbfb45e to
c90cf52
Compare
|
/review |
|
Claude finished @gustavosbarreto's task in 4m 18s —— View job Code Review CompleteReviewed 39 files across code quality, security, testing, TypeScript/React patterns, and architecture — no issues found. All fixes from the previous five review rounds were verified as correctly applied. The code looks good as-is. If you've addressed the feedback and want a new review, tag |
What
Encrypted SSH private key storage in the browser, protected by a master password. Keys are encrypted with AES-256-GCM (PBKDF2-SHA256 derived key) and persisted in localStorage. Private key authentication uses SSH challenge-response signing — the raw key never leaves the browser.
Why
ShellHub previously stored private keys in plain text in localStorage. The Secure Vault encrypts keys at rest behind a master password, matching the security model of tools like Termius Vault. This is the community/self-hosted implementation; the backend adapter pattern (
IVaultBackend) supports future server-side storage for cloud/enterprise.Changes
vault-crypto.ts): AES-256-GCM encryption/decryption, PBKDF2-SHA256 key derivation (600k iterations), verifier-based password checking without storing the password hash. Session key held in module scope (not in React state or localStorage).vault-backend.ts,vault-backend-local.ts):IVaultBackendinterface with localStorage implementation and singleton factory.ssh-keys.ts): sshpk-based key validation withKeyEncryptedErrordetection for auto-detecting passphrase-protected keys, MD5 fingerprint extraction, algorithm/size detection (getAlgorithm), and challenge-response signature generation (RSA via node-rsapkcs1-sha1; ED25519 via sshpk raw 64-byte output; ECDSA via sshpk with per-curve hash — SHA-256/nistp256, SHA-384/nistp384, SHA-512/nistp521 — and SSH wire format blob extraction).vaultStore.ts): Zustand store managing vault lifecycle (uninitialized/locked/unlocked), key CRUD with duplicate name/data prevention, master password change, vault reset, and legacy key migration from the Vue UI format.Ed25519,RSA 4096,ECDSA P-256) and a lock icon for passphrase-protected keys.TerminalInstancenow implements the WebSocket challenge-response protocol (kind 3 Signature) for private key auth — POST sends fingerprint only, browser signs challenges locally. Sensitive key material is cleared immediately after signing and on component unmount.ConnectDraweradds vault key selector with vault/manual toggle and unlock prompt when vault is locked.useKeyFileInputhook andKeyFileInputcomponent, used by both public keys and secure vault drawers.Testing
shellhub-vault-meta,shellhub-vault-data— no plaintext).privateKeysexists in localStorage (legacy Vue UI), verify migration on first vault setup.