125 lines
5.9 KiB
Markdown
125 lines
5.9 KiB
Markdown
# Password Vault
|
||
|
||
An offline-first password manager that runs entirely in your browser. No server, no cloud, no tracking — your vault lives on your machine.
|
||
|
||
## Features
|
||
|
||
- **AES-256-GCM encryption** — All credentials encrypted with a key derived from your master password via PBKDF2 (100,000 iterations). The key exists only in memory.
|
||
- **Zero network calls** — Works from `file://` or `localhost`. No APIs, no analytics, no telemetry.
|
||
- **Group management** — Organize entries into color-coded groups. Create, rename, delete.
|
||
- **Full-text search** — Instant search across title, username, URL, and notes.
|
||
- **Password generator** — Configurable length (4–64), character types, custom exclusions, strength indicator.
|
||
- **Copy to clipboard** — One-click copy with 15-second auto-clear.
|
||
- **JSON import/export** — Export your entire vault as encrypted JSON. Import with merge or replace mode.
|
||
- **Auto-lock** — Vault locks automatically on tab switch, visibility change, or configurable inactivity timer.
|
||
- **Dark theme** — Responsive layout that works on desktop and mobile.
|
||
|
||
## Quick Start
|
||
|
||
```bash
|
||
npm install
|
||
npm run dev # http://localhost:5173
|
||
```
|
||
|
||
## Production Build
|
||
|
||
```bash
|
||
npm run build # → dist/index.html (single self-contained file)
|
||
npm run preview # test the production build locally
|
||
```
|
||
|
||
The build produces a **single `dist/index.html`** file with all JavaScript, CSS, and assets (including favicon) inlined as data URIs. No external files, no network requests — it works from:
|
||
- `file://` protocol (open `dist/index.html` directly)
|
||
- Any static web server (nginx, Apache, GitHub Pages, etc.)
|
||
- USB stick, email attachment, or any offline medium
|
||
|
||
The single-file output is handled by [`vite-plugin-singlefile`](https://www.npmjs.com/package/vite-plugin-singlefile) for JS/CSS inlining, plus a post-build script that inlines the favicon SVG and removes leftover asset files.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
src/
|
||
├── App.svelte # Root — toggles LockScreen ↔ MainLayout
|
||
├── main.js # Entry point
|
||
├── components/
|
||
│ ├── LockScreen.svelte # Vault creation & password unlock
|
||
│ ├── MainLayout.svelte # Dashboard shell (sidebar + content)
|
||
│ ├── Sidebar.svelte # Group list, search, group CRUD
|
||
│ ├── EntryList.svelte # Searchable/filterable entry table
|
||
│ ├── EntryDetail.svelte # View single entry, copy, delete
|
||
│ ├── EntryForm.svelte # Create/edit entry with validation
|
||
│ ├── PasswordGenerator.svelte # Standalone generator panel
|
||
│ └── ImportExport.svelte # JSON import/export modals
|
||
├── lib/
|
||
│ ├── crypto/crypto.js # PBKDF2 key derivation, AES-GCM, generator
|
||
│ ├── models/schema.js # Data factories, validation, ID generation
|
||
│ ├── storage/db.js # IndexedDB wrapper (idb library)
|
||
│ └── stores/
|
||
│ ├── app.svelte.js # Reactive app state (lock/unlock)
|
||
│ ├── search.svelte.js # Shared search + filter state
|
||
│ └── security.svelte.js # Auto-lock timer, visibility detection
|
||
└── styles/
|
||
└── main.css # Dark theme, CSS variables, responsive
|
||
scripts/
|
||
└── inline-assets.js # Post-build: inlines favicon, removes leftover files
|
||
```
|
||
|
||
### Encryption Flow
|
||
|
||
```
|
||
Master Password ──PBKDF2──→ 256-bit Key ──AES-GCM──→ Encrypted Credential
|
||
(100k iters)
|
||
│
|
||
└── Salt stored in IndexedDB (not encrypted)
|
||
```
|
||
|
||
- The encryption key is **never persisted** — it lives only in JavaScript memory.
|
||
- A test payload (random string) is encrypted on vault creation and stored alongside the salt. On unlock, the entered password is used to derive a key and decrypt the test payload — if decryption succeeds, the password is correct.
|
||
- On tab close or auto-lock, the key is cleared from memory.
|
||
|
||
### Storage Schema (IndexedDB)
|
||
|
||
| Store | Fields |
|
||
|---|---|
|
||
| `entries` | `id`, `title`, `username`, `password` (encrypted), `url`, `notes` (encrypted), `groupId`, `createdAt`, `updatedAt` |
|
||
| `groups` | `id`, `name`, `color`, `createdAt` |
|
||
| `meta` | `salt`, `testEncrypted`, `testPlaintext` |
|
||
|
||
## Security Considerations
|
||
|
||
| Threat | Mitigation |
|
||
|---|---|
|
||
| Key persistence | Key stored only in `$state`, cleared on lock/close |
|
||
| Weak passwords | Strength indicator on generator |
|
||
| Clipboard leakage | Auto-clear after 15 seconds |
|
||
| Tab left open | Auto-lock on visibility change (tab switch) |
|
||
| Database tampering | All sensitive data encrypted at rest with AES-256-GCM |
|
||
| Brute force | PBKDF2 with 100,000 iterations slows offline attacks |
|
||
|
||
### Known limitations
|
||
|
||
- **No browser fingerprinting or anti-keylogger** — This is a local tool, not a hardened security appliance.
|
||
- **IndexedDB can be inspected** — Encrypted data is safe, but metadata (titles, usernames, URLs) may be visible if not encrypted. Currently only `password` and `notes` are encrypted; titles/usernames/URLs are stored in plaintext for searchability.
|
||
- **No automatic backups** — Use the JSON export feature to back up your vault regularly.
|
||
|
||
## Development
|
||
|
||
```bash
|
||
npm run dev # Start dev server with HMR
|
||
npm run build # Production build (zero warnings target)
|
||
npm run preview # Preview production build
|
||
```
|
||
|
||
### Stack
|
||
|
||
- **Svelte 5** — Runes-based reactivity (`$state`, `$derived`, `$effect`), props-based event passing
|
||
- **Vite 8** — Build tool and dev server
|
||
- **vite-plugin-singlefile** — Inlines all JS/CSS into a single HTML file
|
||
- **idb** — Promise-based IndexedDB wrapper
|
||
- **Web Crypto API** — Native browser cryptography (no external crypto libraries)
|
||
- **Vanilla CSS** — Dark theme with CSS custom properties, no preprocessors
|
||
|
||
## License
|
||
|
||
MIT
|