From c0231fcd26f503f8c95f2cdd365d75d5217aa772 Mon Sep 17 00:00:00 2001 From: Timothy Farrell Date: Fri, 15 May 2026 21:07:09 +0000 Subject: [PATCH] Add the AGENTs file to cut down on model code analysis --- AGENTS.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0392f3e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,72 @@ +# Password Vault — Agent Context + +## Project Overview + +An offline-first, single-file password manager built with **Svelte 5** (runes-based reactivity). All data is encrypted in-browser with AES-256-GCM and stored in IndexedDB. The production build is a single `dist/index.html` with zero network dependencies — it works from `file://`, USB, or any static server. + +## Architecture + +``` +src/ +├── App.svelte # Root component — routes between LockScreen, MainLayout +├── main.js # Entry point +├── components/ +│ ├── LockScreen.svelte # Master password setup + unlock UI +│ ├── MainLayout.svelte # Shell: sidebar + content area +│ ├── Sidebar.svelte # Group list + search bar +│ ├── EntryList.svelte # Credential entries grid +│ ├── EntryForm.svelte # Create/edit credential form +│ ├── EntryDetail.svelte # View single entry (copy password/username) +│ ├── PasswordGenerator.svelte# Configurable password generator +│ └── ImportExport.svelte # JSON import/export with merge/replace +├── lib/ +│ ├── crypto/crypto.js # Web Crypto API: PBKDF2 key derivation, AES-GCM encrypt/decrypt, password generator +│ ├── storage/db.js # IndexedDB layer (idb wrapper): entries, groups, meta stores +│ ├── models/schema.js # Data models: CredentialEntry, Group; validation; ID generation +│ └── stores/ +│ ├── app.svelte.js # AppStore: $state(isUnlocked, encryptionKey, salt) +│ ├── security.svelte.js # Auto-lock timer, visibility change, beforeunload cleanup +│ └── search.svelte.js # Reactive search state +``` + +## Key Design Decisions + +- **Svelte 5 runes** — Use `$state`, `$derived`, `$effect`. Props-based event passing (no Svelte events). +- **No external crypto libraries** — Uses the browser's native Web Crypto API exclusively. +- **Key never persisted** — Encryption key lives only in `$state` memory; cleared on lock, tab switch, or page close. +- **Single-file build** — `vite-plugin-singlefile` inlines all JS/CSS; post-build script inlines favicon. +- **IndexedDB via `idb`** — Three stores: `entries`, `groups`, `meta`. Only `encryptedPassword` is encrypted at rest; titles, usernames, URLs, and notes are plaintext for searchability. +- **PBKDF2 key derivation** — 600,000 iterations, SHA-256, 256-bit AES-GCM key. + +## Encryption Flow + +``` +Master Password → PBKDF2 (600k iters, 16-byte salt) → AES-256-GCM Key → encrypt/decrypt credentials +``` + +Password verification uses a test payload (random string encrypted at vault creation). On unlock, the entered password derives a key that must successfully decrypt the test payload. + +## Scripts + +| Command | Description | +|---|---| +| `npm run dev` | Vite dev server with HMR on `:5173` | +| `npm run build` | Production build → `dist/index.html` (single self-contained file) | +| `npm run preview` | Preview production build | +| `npm run test` | Vitest (watch mode) | +| `npm run test:run` | Vitest (single run) | + +## Testing + +- Framework: **Vitest** with **jsdom** environment +- Test setup: `tests/setup.js` (fake-indexeddb polyfill) +- Test files: `tests/lib/stores/*.test.js` +- Run with `npm run test` or `npm run test:run` + +## Security Notes + +- Only `encryptedPassword` is encrypted at rest; other fields (title, username, URL, notes) are plaintext in IndexedDB. +- `testPlaintext` for password verification is stored unencrypted in the `meta` store. +- Auto-lock triggers on tab visibility change and 5-minute inactivity. +- Clipboard auto-clears after 15 seconds. +- No browser fingerprinting or anti-keylogger protections.