7.7 KiB
@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
pnpm add @lilith/vault-setup-backup
Usage
Programmatic API
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
- Authentication: GCM mode provides authenticated encryption (prevents tampering)
- Unique encryption: Each backup has unique salt and IV
- Hash verification: SHA-256 hash detects file corruption
- Key derivation: PBKDF2 makes brute-force attacks computationally expensive
- 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
import { backupVault } from '@lilith/vault-setup-backup'
const result = await backupVault({
source: '~/.vault/',
destination: '~/Documents/VaultBackups/',
masterPassword: 'my-secure-password',
})
Scheduled Backups
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
import { pruneBackups } from '@lilith/vault-setup-backup'
// Keep last 30 backups, delete older
const result = await pruneBackups('~/Documents/VaultBackups/', 30)
Change Master Password
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:
- Primary: Restic backups to REST server (every 5min for code, 12hr for dotfiles)
- Secondary: This encrypted backup app (every 6hr, stored locally)
- 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
# 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!