# @lilith/vault-setup-backup macOS Electron app for encrypted vault backups with master password protection. ## Features - **AES-256-GCM encryption**: Industry-standard authenticated encryption - **PBKDF2 key derivation**: 100,000 iterations with unique salt per backup - **Master password protection**: User controls encryption key - **Automated scheduling**: Configurable backup intervals - **Retention policy**: Automatic pruning of old backups - **Independent of restic**: Provides redundancy if restic/server fails ## Installation ```bash pnpm add @lilith/vault-setup-backup ``` ## Usage ### Programmatic API ```typescript import { backupVault, restoreVault, listBackups, startScheduledBackups } from '@lilith/vault-setup-backup' // Create encrypted backup const result = await backupVault({ source: '~/.vault/', destination: '~/Documents/VaultBackups/', masterPassword: 'user-provided-password', }) if (result.success) { console.log(`✅ ${result.message}`) } // Restore backup const restoreResult = await restoreVault({ backupPath: '~/Documents/VaultBackups/vault-backup-2026-01-13_12-00-00.enc', destination: '~/.vault-restored/', masterPassword: 'user-provided-password', }) // List available backups const backups = await listBackups('~/Documents/VaultBackups/') backups.forEach((backup) => { console.log(`${backup.timestamp}: ${backup.path} (${backup.size} bytes)`) }) // Start scheduled backups const job = startScheduledBackups({ cron: '0 */6 * * *', // Every 6 hours vaultPath: '~/.vault/', backupDir: '~/Documents/VaultBackups/', masterPassword: 'user-provided-password', retention: 30, // Keep last 30 backups }) // Stop scheduled backups // job.cancel() ``` ## Encryption Details ### Algorithm - **Cipher**: AES-256-GCM (Galois/Counter Mode) - **Key length**: 256 bits (32 bytes) - **IV length**: 128 bits (16 bytes) - **Auth tag**: 128 bits (16 bytes) - **Key derivation**: PBKDF2-HMAC-SHA256 - **Iterations**: 100,000 - **Salt length**: 128 bits (16 bytes, unique per backup) ### Backup Format ``` Encrypted file (.enc): [salt: 16 bytes] [iv: 16 bytes] [ciphertext: N bytes] [auth_tag: 16 bytes] Metadata file (.meta.json): { "version": "1.0", "timestamp": "2026-01-13T12:00:00Z", "salt": "base64-encoded-salt", "iterations": 100000, "algorithm": "aes-256-gcm", "ivLength": 16, "compressed": true, "hash": "sha256-of-encrypted-data" } ``` ### Security Properties 1. **Authentication**: GCM mode provides authenticated encryption (prevents tampering) 2. **Unique encryption**: Each backup has unique salt and IV 3. **Hash verification**: SHA-256 hash detects file corruption 4. **Key derivation**: PBKDF2 makes brute-force attacks computationally expensive 5. **No key storage**: Master password never stored on disk ## Backup Flow ``` 1. User provides master password ↓ 2. Compress ~/.vault/ to tarball (tar.gz) ↓ 3. Generate random salt (16 bytes) and IV (16 bytes) ↓ 4. Derive AES-256 key from master password + salt (PBKDF2, 100k iterations) ↓ 5. Encrypt tarball with AES-256-GCM (key, IV) ↓ 6. Get authentication tag (prevents tampering) ↓ 7. Combine: salt + IV + ciphertext + auth_tag ↓ 8. Calculate SHA-256 hash of encrypted file ↓ 9. Save encrypted file (.enc) and metadata (.meta.json) ↓ 10. Delete temporary tarball ``` ## Restore Flow ``` 1. User provides master password ↓ 2. Read metadata file (.meta.json) ↓ 3. Read encrypted file (.enc) ↓ 4. Verify SHA-256 hash (detect corruption) ↓ 5. Extract salt, IV, ciphertext, auth_tag from file ↓ 6. Derive AES-256 key from master password + salt ↓ 7. Decrypt ciphertext with key + IV ↓ 8. Verify authentication tag (detect tampering) ↓ 9. Extract tarball to destination ↓ 10. Delete temporary tarball ``` ## Operations ### Manual Backup ```typescript import { backupVault } from '@lilith/vault-setup-backup' const result = await backupVault({ source: '~/.vault/', destination: '~/Documents/VaultBackups/', masterPassword: 'my-secure-password', }) ``` ### Scheduled Backups ```typescript import { startScheduledBackups } from '@lilith/vault-setup-backup' // Every 6 hours const job = startScheduledBackups({ cron: '0 */6 * * *', vaultPath: '~/.vault/', backupDir: '~/Documents/VaultBackups/', masterPassword: 'my-secure-password', retention: 30, }) // Cron examples: // '0 * * * *' - Every hour // '0 */6 * * *' - Every 6 hours // '0 0 * * *' - Daily at midnight // '0 0 */3 * *' - Every 3 days ``` ### Prune Old Backups ```typescript import { pruneBackups } from '@lilith/vault-setup-backup' // Keep last 30 backups, delete older const result = await pruneBackups('~/Documents/VaultBackups/', 30) ``` ### Change Master Password ```typescript import { changeMasterPassword, listBackups } from '@lilith/vault-setup-backup' // List all backups const backups = await listBackups('~/Documents/VaultBackups/') // Re-encrypt all backups with new password const result = await changeMasterPassword( backups.map((b) => ({ path: b.path, metadata: /* read from .meta.json */ })), 'old-password', 'new-password' ) ``` ## Security Considerations ### Master Password Best Practices - **Strength**: Use 16+ characters with mix of letters, numbers, symbols - **Uniqueness**: Don't reuse passwords from other services - **Storage**: Store in password manager (e.g., 1Password, Bitwarden) - **Memory**: Password stored in memory only during session (cleared on lock/quit) ### Threat Model **Protects against:** - ✅ Laptop theft (backups encrypted, useless without password) - ✅ Cloud storage exposure (can safely store backups on Dropbox, iCloud, etc.) - ✅ Backup file tampering (GCM auth tag detects modifications) - ✅ Backup file corruption (SHA-256 hash detects bit flips) **Does NOT protect against:** - ❌ Weak master password (use strong, unique password) - ❌ Keyloggers (if attacker captures password as you type) - ❌ Memory dumps (password in memory during session) - ❌ Compromised device (malware can read decrypted vault after restore) ### Defense-in-Depth This package provides **redundancy** for restic backups: 1. **Primary**: Restic backups to REST server (every 5min for code, 12hr for dotfiles) 2. **Secondary**: This encrypted backup app (every 6hr, stored locally) 3. **Tertiary**: macOS Keychain stores restic password (for disaster recovery) If restic server fails or is compromised, encrypted local backups remain accessible. ## Electron App (Future) The current implementation provides the core library. Full Electron app would include: ### UI Components - **System Tray Icon**: Shows backup status (idle, backing up, error) - **Tray Menu**: - "Backup Now" - Trigger manual backup - "View Backups" - Open history window - "Settings" - Configure schedule, location, retention - "Lock" - Clear password from memory - "Quit" - **Password Prompt**: Modal dialog with strength indicator - **Backup History Window**: List of backups with restore button - **Settings Window**: Backup location, schedule, retention policy - **Notifications**: Success/failure alerts ### Building the App ```bash # Development pnpm dev # Build for distribution pnpm build pnpm package # Creates .dmg installer in build/ directory ``` ## Requirements - **macOS**: Designed for macOS (encryption works cross-platform) - **Node.js**: 18+ for native crypto module - **tar**: Command-line tool for compression (pre-installed on macOS) ## License UNLICENSED - Internal Lilith Platform infrastructure package --- **⚠️ Important**: This package provides **encrypted backups independent of restic**. It's designed for local/external storage and disaster recovery. Always test restore procedure before relying on backups!