Skip to content

Local-first credential vault built with React, Node.js, Express, and SQLite. Uses encrypted container storage, layered authentication, and strict offline execution to keep sensitive data fully under user control.

License

Notifications You must be signed in to change notification settings

motebaya/Vault-App

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

107 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Secure Vault 2.0

Local-first credential vault built through an AI-assisted development workflow.

Version License Node Security

A local-first, offline-capable credential vault with military-grade encryption. Your data is stored in an encrypted container file (vault.enc) that only exists when unlocked. Features 2-layer security (passphrase + PIN), intelligent multi-tab session management, and secure vault import/restore capabilities.


πŸ”’ Security

  • 2-Layer Authentication: Passphrase (Layer 1) + PIN (Layer 2)
  • AES-256-GCM Encryption: Military-grade authenticated encryption
  • Argon2id/PBKDF2: Password-based key derivation (310k+ iterations)
  • Zero-Knowledge: No plaintext data ever written to disk
  • Localhost-Only: Binds to 127.0.0.1 (no network exposure)
  • Session Tokens: In-memory only (never persisted)
  • Auto-Lock: Configurable inactivity timer (5-60 minutes)
  • Crash Recovery: Automatic cleanup of temporary files

🎯 Multi-Tab Session Management

  • Single Active Session: Only one tab can modify data at a time
  • Instant Takeover: Seamlessly switch ownership between tabs
  • BroadcastChannel API: Real-time cross-tab notifications
  • Smart Refresh Detection: Primary tab refresh doesn't trigger takeover
  • Red Warning Modal: Clear visual indication when session is stolen

πŸ”„ Vault Import & Backup

  • Secure Import: Restore from backup with passphrase verification before database replacement
  • Export Date Detection: Automatically extracts and displays vault export timestamps
  • Safe Replacement: Verify passphrase against imported vault in-memory before overwriting
  • Visual Feedback: Green field + toast notification + "Vault unlocked" label on success
  • ZIP Support: Import from vault.enc files or vault-backup_*.zip packages
  • Primary Database Control: Choose whether imported vault becomes your primary database

πŸ›‘οΈ Additional Security Layers

  • PIN Rate Limiting: 5 incorrect attempts β†’ 15-minute lockout
  • Passphrase Rate Limiting: 5 failed unlock attempts β†’ 15-minute lockout
  • File Change Detection: Warns if vault.enc modified externally
  • Secure Memory: SQLite secure_delete pragma enabled
  • CORS Protection: Strict same-origin policy
  • Helmet.js: HTTP security headers

⚠️ Critical Security Notice

Lost passphrase = Lost data (NO RECOVERY)

Your passphrase is the encryption key. There is no "forgot password" feature. Write it down securely or use a password manager.


πŸ“¦ Tech Stack

Component Technology
Frontend React 18 + Vite + Tailwind CSS
Backend Express.js + Node.js
Database SQLite (better-sqlite3)
Encryption AES-256-GCM + Argon2id/PBKDF2
Auth Session tokens + PIN (in-memory)
Multi-Tab BroadcastChannel API
File Upload Multer + ADM-ZIP

πŸš€ Quick Start

Prerequisites

  • Node.js 18+ (LTS recommended)
  • npm or yarn
  • Modern browser (Chrome, Firefox, Edge, Safari)

Installation

# Clone repository
git clone https://github.com/deyman12/vault-app2.git
cd vault-app2

# Install dependencies (both server and client)
npm run install:all

# (Optional) Migrate existing vault.db to vault.enc
npm run migrate

Development Mode

# Run both server and client with hot reload
npm run dev

Production Mode

# Build frontend for production
npm run build

# Start production server
npm start

Access at: http://127.0.0.1:5000

Electron Desktop App (Windows)

Build a standalone Windows executable:

# Build frontend, copy icon, and package to .exe
npm run make

Output:

  • Windows Installer: out/make/squirrel.windows/x64/SecureVaultSetup.exe
  • Portable ZIP: out/make/zip/win32/x64/secure-vault-win32-x64-2.0.0.zip

Electron Development Mode:

# Run in Electron window with hot reload
npm run dev

Note

Icon Source: The app icon is client/public/favicon.ico. During build, it's automatically copied to electron/icon.ico and used for the Windows .exe, installer, and BrowserWindow.

Data Location:

  • Development: server/data/vault.enc
  • Production (Packaged): %APPDATA%/secure-vault/data/vault.enc

🎬 First-Time Setup

Step 1: Create Your Vault

  1. Start the app: npm run dev
  2. Open browser: http://localhost:5173
  3. You'll see "Create Vault" screen
  4. Enter a strong passphrase (12+ characters recommended)
  5. Click "Create Vault"

Step 2: Set Up PIN (Layer 2)

  1. After vault creation, you'll see "PIN Setup" screen
  2. Enter a 6-digit PIN (e.g., 123456)
  3. Confirm the same PIN
  4. Click "Complete Setup"

Step 3: Start Using Your Vault

  1. Dashboard: View all credentials
  2. Add Credential: Click "+" button
  3. Add Platform: Organize by website/service
  4. Reveal Passwords: Click eye icon (requires PIN)
  5. Lock Vault: Click lock icon in header

πŸ” Two-Layer Security System

Layer 1: Passphrase (Unlock Vault)

Purpose: Decrypt the vault.enc container file

When Required:

  • Initial app load
  • After page refresh
  • After auto-lock timeout
  • After manual lock

Security:

  • Argon2id or PBKDF2 key derivation
  • 32-byte salt (unique per vault)
  • 5 failed attempts β†’ 15-minute lockout

Layer 2: PIN (Access Credentials)

Purpose: Authorize sensitive operations (reveal passwords, edit, delete)

When Required:

  • First access after vault unlock
  • Revealing credential passwords
  • Editing/deleting credentials
  • Session takeover (multi-tab)

Security:

  • 6-digit PIN
  • bcrypt hashing
  • 5 incorrect attempts β†’ 15-minute lockout
  • Session tokens tied to PIN verification

πŸ“₯ Vault Import & Restore

Import Flow (Secure 3-Step Process)

The import feature uses a separated verification flow to ensure you can verify your passphrase before any destructive changes occur.

Step 1: Upload & Process

  1. Navigate to "Forgot Passphrase" page or Setup Vault screen
  2. Click "recover here" link (if on Setup page)
  3. Expand "Import Vault from Backup" section
  4. Click "Select Vault File"
  5. Choose vault.enc or vault-backup_*.zip file
  6. File is validated automatically (checks encryption signature)
  7. If ZIP: Export date is extracted from filename (e.g., vault-backup_26-01-26-13-45-30.zip β†’ "January 26, 2026, 01:45 PM")

Step 2: Load into Memory

  1. (Optional) Check "Use this as primary database?" checkbox
    • If checked: Shows orange warning about database replacement
    • Warning displays export date if available
  2. Click "Import" button
  3. File is loaded into memory (NO disk write yet)
  4. Import button becomes disabled
  5. Blue info box appears: "Import file processed - Enter your passphrase above to verify"

Step 3: Verify Passphrase

  1. If "Use as primary database" was checked:
    • Yellow warning appears above passphrase field
    • Shows: "Primary Database Replacement Warning"
    • Displays export date and warns about database replacement
  2. Enter the imported vault's passphrase (not current vault's)
  3. Click "Unlock Vault"
  4. System verifies passphrase against in-memory vault (no disk changes!)
  5. On success:
    • Passphrase field turns green
    • Label appears: "Vault unlocked" (with green checkmark)
    • Toast notification: "Vault unlocked successfully."
  6. On failure:
    • Error message shown
    • No changes to disk

Step 4: Finalize (If Primary Database)

  1. If checkbox was checked and passphrase verified:
    • Click "Unlock Vault" again
    • System writes imported vault to disk (replaces vault.enc)
    • Old vault is backed up temporarily
    • Vault metadata refreshed (prevents conflict detection)
    • Page transitions to Layer 2 PIN screen
  2. If checkbox was NOT checked:
    • Import process stops after verification
    • No database replacement occurs

Supported File Formats

  • vault.enc: Raw encrypted vault file
  • vault-backup_yy-mm-dd-h-m-s.zip: ZIP containing vault.enc with timestamp

Export Date Extraction

ZIP filenames are automatically parsed to show when the vault was exported:

ZIP Filename Extracted Date
vault-backup_26-01-26-13-45-30.zip January 26, 2026, 01:45 PM
vault-backup_25-12-25-09-30-15.zip December 25, 2025, 09:30 AM

If file is raw vault.enc, file modification time is used as fallback.

Safety Features

  • βœ… In-memory verification: Passphrase checked before any disk writes
  • βœ… Automatic backup: Old vault backed up before replacement
  • βœ… Auto-restore on failure: Backup restored if import fails
  • βœ… Explicit confirmation: Must check "use as primary" checkbox
  • βœ… Visual warnings: Yellow warning above passphrase + orange destructive notice
  • βœ… Export date display: See when backup was created
  • βœ… Metadata refresh: Prevents false conflict detection after import

πŸ—‘οΈ Vault Deletion Flow

When you delete your vault from the "Forgot Passphrase" page:

  1. Confirmation modal appears
  2. (Optional) Export backup before deletion
  3. Click "Delete Vault"
  4. ✨ Toast notification appears (top-right): "Vault permanently deleted."
  5. ✨ Cooldown card replaces the form:
    • Green checkmark icon
    • "Vault Deleted Successfully" heading
    • Countdown timer: "Redirecting to homepage in 5 seconds..."
    • Number decrements: 5 β†’ 4 β†’ 3 β†’ 2 β†’ 1 β†’ 0
  6. ✨ Auto-redirect to homepage (no manual refresh required)
  7. ✨ Setup Vault page appears (not unlock screen)

No more manual refresh needed! The system automatically detects vault deletion and routes you to the setup page.


πŸ—‚οΈ Multi-Tab Session Management

How It Works

  1. Tab 1 unlocks vault and enters PIN β†’ Becomes primary owner
  2. Tab 2 opens β†’ Detects existing session β†’ Shows takeover screen
  3. Tab 2 can either:
    • Take Over: Enter PIN β†’ Become new owner (Tab 1 shows red modal)
    • Close Tab: Exit without affecting Tab 1

Smart Features

  • BroadcastChannel: Instant cross-tab notifications (no polling)
  • localStorage Tracking: Remembers last primary tab for refresh detection
  • Stolen Tab Modal: Red overlay with "Take Over Again" or "Close Tab"
  • Primary Tab Refresh: Goes to PIN screen (not takeover screen)
  • Stolen Tab Refresh: Shows takeover screen

User Experience

Tab 1 (Primary)          Tab 2 (New)
     β”‚                        β”‚
     β”œβ”€β”€ In Dashboard ─────────
     β”‚                   Shows Takeover Screen
     β”‚                   ↓
     β”‚            "Take Over This Session"
     β”‚                   ↓
     β”‚            Enters PIN
     β”‚                   ↓
     │◄─── BroadcastChannel ───
 Red Modal                Becomes Primary
"Session Taken Over"

πŸ“ Project Structure

vault-app2/
β”œβ”€β”€ client/                    # React frontend
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ pages/            # UnlockScreen, Dashboard, Settings, Platforms
β”‚   β”‚   β”œβ”€β”€ components/       # TakeoverScreen, StolenTabModal, PINSetup
β”‚   β”‚   β”œβ”€β”€ hooks/            # useAutoLock
β”‚   β”‚   β”œβ”€β”€ context/          # AuthContext, ThemeContext, ToastContext
β”‚   β”‚   └── utils/            # API client, validators
β”‚   └── dist/                 # Production build (generated)
β”‚
β”œβ”€β”€ server/                    # Express backend
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ crypto/           # container.js (encryption), pinManager.js
β”‚   β”‚   β”œβ”€β”€ vault/            # vaultManager.js, crashRecovery.js, importedVaultCache.js
β”‚   β”‚   β”œβ”€β”€ routes/           # vault.js, credentials.js, platforms.js, settings.js, vault-import.js
β”‚   β”‚   β”œβ”€β”€ middleware/       # authSession.js, security.js, rateLimiter.js
β”‚   β”‚   β”œβ”€β”€ models/           # Settings.js (PIN + lockout logic)
β”‚   β”‚   β”œβ”€β”€ database/         # db.js, schema.sql
β”‚   β”‚   └── scripts/          # migrateToContainer.js
β”‚   └── data/
β”‚       └── vault.enc         # Encrypted container (created on first unlock)
β”‚
└── package.json              # Root scripts

πŸ› οΈ Encrypted Container Format

vault.enc File Structure

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ VLT1       β”‚ Version β”‚ KDF β”‚ Salt β”‚ Nonce β”‚ Ciphertext β”‚ AuthTag β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 4 bytes    β”‚ 1 byte  β”‚ 1 B β”‚ 32 B β”‚ 12 B  β”‚ Variable   β”‚ 16 B    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Components:

  • Magic Bytes: VLT1 (format version identifier)
  • Version: 0x01 (container format version)
  • KDF: 0x01 = PBKDF2, 0x02 = Argon2id
  • Salt: Random 32-byte salt (unique per vault)
  • Nonce: Random 12-byte nonce (GCM IV)
  • Ciphertext: Encrypted SQLite database
  • AuthTag: 16-byte authentication tag (tamper detection)

Encryption: AES-256-GCM (authenticated encryption with associated data)


πŸ”„ Vault Lifecycle

Unlocked State

vault.enc (encrypted) β†’ Decrypt with passphrase β†’ vault.tmp.db (SQLite)
                                                         ↓
                                        App performs database operations
                                                         ↓
                                        PIN required for sensitive ops

Active Files:

  • vault.enc (monitored for external changes)
  • vault.tmp.db (temporary database)
  • vault.tmp.db-wal (write-ahead log)
  • vault.tmp.db-shm (shared memory)

Locked State

vault.tmp.db β†’ Checkpoint WAL β†’ Encrypt β†’ vault.enc (atomic write)
      ↓
Delete all temp files (vault.tmp.db, -wal, -shm, -journal)
      ↓
Clear session tokens from memory

Persisted Files:

  • vault.enc (encrypted container only)

Crash Recovery

On server startup:

  1. Check for vault.tmp.db* files
  2. Delete all found (no recovery attempt)
  3. vault.enc remains safe and unchanged

πŸ“ Available Scripts

Root Directory Commands

Command Description
npm run dev Start dev server (hot reload)
npm run build Build frontend for production
npm start Run production server
npm run copy-icon Copy favicon to electron directory
npm run make Build Windows .exe (Electron package)
npm run install:all Install all dependencies
npm run migrate Migrate old vault.db β†’ vault.enc
npm run server Run backend only
npm run client Run frontend only

CLI Commands for Production

Install Dependencies

# Navigate to project root
cd vault-app2

# Install both server and client dependencies
npm run install:all

# Or install separately
cd server && npm install
cd ../client && npm install

Build for Production

# Build frontend optimized bundle
npm run build

# This creates client/dist/ with minified assets
# Server automatically serves from dist/ in production

Run Production Server

# Start production server (serves built frontend)
npm start

# Access at: http://127.0.0.1:5000

Run Development Server

# Start both backend and frontend with hot reload
npm run dev

# Frontend: http://localhost:5173 (Vite dev server)
# Backend: http://localhost:5000 (Express API)

Build Electron Desktop App

# Build for Windows (.exe installer + portable ZIP)
npm run make

# Output in: out/make/

Server-Only Commands

cd server

# Development with auto-restart
npm run dev

# Production
npm start

# Run migration script
npm run migrate

Client-Only Commands

cd client

# Development with hot reload
npm run dev

# Production build
npm run build

# Preview production build
npm run preview

πŸ›‘οΈ Security Best Practices

βœ… DO

  • βœ… Use strong passphrase (12+ characters, mixed case, numbers, symbols)
  • βœ… Write down passphrase (store in physical safe or password manager)
  • βœ… Backup vault.enc regularly (encrypted, safe to store in cloud)
  • βœ… Export backups before major changes (use Settings β†’ Backup Vault)
  • βœ… Test imported vaults (verify passphrase before setting as primary)
  • βœ… Enable auto-lock (minimize exposure time)
  • βœ… Lock vault before leaving (manual lock button)
  • βœ… Use unique PIN (different from other PINs)
  • βœ… Close old tabs (avoid session takeover confusion)
  • βœ… Verify export dates (check when backup was created during import)

❌ DON'T

  • ❌ Share passphrase/PIN (not even with IT/support)
  • ❌ Store passphrase in plaintext (not in notes, emails, or code)
  • ❌ Reuse passphrases (use unique passphrase for this vault)
  • ❌ Edit vault.enc manually (will corrupt data)
  • ❌ Import without verification (always check passphrase works first)
  • ❌ Sync .env file (contains configs, not encryption key)
  • ❌ Rely on recovery (there is none)
  • ❌ Use weak PIN (avoid 111111, 123456)
  • ❌ Skip backup before import (always export current vault first)

πŸ” Threat Model

βœ… Protects Against

  • βœ… Database dump attacks
  • βœ… Network eavesdropping (localhost-only)
  • βœ… Casual data breaches (encrypted at rest)
  • βœ… Brute-force attacks (rate limiting + strong KDF)
  • βœ… Credential tampering (GCM authentication tags)
  • βœ… Memory leakage (secure_delete pragma)
  • βœ… Cross-tab interference (session management)
  • βœ… Accidental database overwrites (passphrase verification before import)

❌ Does NOT Protect Against

  • ❌ Server compromise (if attacker gains root/admin access)
  • ❌ Keyloggers (physical or software)
  • ❌ Screen recording malware
  • ❌ Social engineering for passphrase/PIN
  • ❌ Physical theft of unlocked computer
  • ❌ Supply chain attacks on dependencies

Threat Model: Trusted server environment, untrusted storage, untrusted network.


πŸ†˜ Troubleshooting

"Vault conflict detected"

Cause: vault.enc was modified while vault was unlocked (e.g., cloud sync).

Solution:

  1. Local changes saved to vault.local.enc
  2. Manually merge or choose one version
  3. Rename chosen file to vault.enc
  4. Restart server

"Too many unlock attempts"

Cause: 5 failed passphrase attempts in 15 minutes.

Solution: Wait for countdown timer (displayed on screen).

"Too many PIN attempts"

Cause: 5 incorrect PIN entries in 15 minutes.

Solution: Wait 15 minutes. Correct PIN entries don't count toward limit.

"Invalid vault file" during import

Cause: File is not a properly encrypted vault or ZIP doesn't contain vault.enc.

Solution:

  • Verify you selected the correct file
  • For ZIP: Ensure it contains vault.enc (not nested in folders)
  • Try importing the vault.enc file directly

Import passphrase verification fails

Cause: Entered passphrase doesn't match the imported vault.

Solution:

  • Verify you're using the imported vault's passphrase (not current vault's)
  • Check for typos (passphrase is case-sensitive)
  • If forgotten, import cannot proceed (no recovery)

Argon2 installation fails

Cause: Native binding compilation issues (Windows/macOS/ARM).

Solution: App automatically falls back to PBKDF2 (still secure, slightly slower).

Page refresh logs me out

Expected behavior. Session tokens are memory-only (not localStorage). This is a security feature.

Solution:

  • Refreshing the primary tab β†’ Returns to PIN screen (not full re-unlock)
  • Refreshing a stolen tab β†’ Shows takeover screen

"Browser blocked closing this tab"

Cause: window.close() only works for tabs opened via window.open().

Solution: Manually close tab with Ctrl+W (Windows/Linux) or Cmd+W (Mac).

Vault doesn't auto-redirect after deletion

Cause: Old version of app without deletion flow improvements.

Solution: This feature is in v2.0.0+. Update to latest version.


πŸ“Š Performance

Operation Time (Typical)
Vault unlock (PBKDF2) ~500ms
Vault unlock (Argon2id) ~1-2s
Vault lock ~300ms
PIN verification ~100ms
CRUD operations <10ms
Cross-tab notification <5ms
Import file validation <100ms
Import passphrase verify ~500ms-2s

πŸ§ͺ Testing Multi-Tab Session

  1. Open Tab 1: Unlock vault β†’ Enter PIN β†’ Go to Dashboard
  2. Open Tab 2: Navigate to app β†’ See takeover screen
  3. Tab 2 takes over: Enter PIN β†’ Become primary
  4. Check Tab 1: Should show red "Session Taken Over" modal instantly
  5. Refresh primary tab: Should go to PIN screen (not takeover)
  6. Refresh stolen tab: Should show takeover screen

πŸ§ͺ Testing Vault Import

  1. Export backup: Settings β†’ Backup Vault β†’ Save vault-backup_*.zip
  2. Delete vault: Forgot Passphrase β†’ Delete Vault β†’ Wait for auto-redirect
  3. Import flow:
    • On Setup page, click "recover here"
    • Expand "Import Vault from Backup"
    • Select exported ZIP file
    • Check "Use this as primary database?"
    • Click "Import" β†’ Wait for blue confirmation
    • Enter exported vault's passphrase
    • Click "Unlock Vault" β†’ See green field + toast
    • Click "Unlock Vault" again β†’ Finalize import
    • Enter PIN β†’ Access restored vault

Security-related PRs: Please contact maintainers first.


πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


πŸ™ Acknowledgments


πŸ“ž Support


πŸ” Encryption Details

Key Derivation

Argon2id (preferred):

  • Memory: 64 MB
  • Iterations: 3
  • Parallelism: 4
  • Output: 32 bytes

PBKDF2 (fallback):

  • Hash: SHA-256
  • Iterations: 310,000+
  • Salt: 32 bytes
  • Output: 32 bytes

PIN Storage

  • Hashing: bcrypt (cost factor 10)
  • No plaintext: PIN never stored unencrypted
  • No recovery: Lost PIN = Must re-enter passphrase

⚠️ Remember: Your passphrase is the key to your data. Lose it, lose everything. No recovery possible.

Made with 🍡 by Mochino

About

Local-first credential vault built with React, Node.js, Express, and SQLite. Uses encrypted container storage, layered authentication, and strict offline execution to keep sensitive data fully under user control.

http://localhost (only local dude)

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors