hermes-explorigin 179bc83bfe
All checks were successful
Test, Build & Deploy / test-and-build (push) Successful in 55s
Test, Build & Deploy / deploy (push) Successful in 26s
Add PWA support with a network-sandbox for the installed app
Makes the offline-first vault installable as a standalone PWA, and enforces
"no network access aside from the app's own resources" once installed.

PWA:
- public/manifest.webmanifest: scope ./ (confined to the app's own dir), three
  icons (192/512 + transparent maskable), standalone display, brand theme. No
  push/shortcuts (deliberate: offline-first vault, nothing to push).
- public/sw.js: a NETWORK SANDBOX service worker. Allows only same-origin
  requests inside the worker's own directory (index, manifest, icons); returns
  403 for every cross-origin request and for same-origin paths outside the app
  dir. This blocks in-app <img>/<script>/fetch exfiltration at the source.
- src/lib/pwa.js + main.js: register the worker (relative path, works at / in
  dev and under /password_manager/ in prod).
- index.html: dev CSP (permits Vite HMR WebSocket) + PWA meta/link tags.

Security:
- scripts/inline-assets.js swaps the dev CSP for the STRICT production CSP in
  the shipped dist: connect-src 'none' (no fetch/XHR/WebSockets anywhere),
  form-action 'none', object-src/base-uri 'none', img/font/media 'self' data:.
  The browser-native connect-src closes the WebSocket gap the SW cannot see.
- Known boundary documented in-file: frame-ancestors / X-Frame-Options is
  HTTP-header-only (ignored in meta) and not set by the static host, so
  clickjacking is permissive; all exfiltration channels are closed regardless.

Build/deploy:
- inline-assets.js now PRESERVES manifest/sw.js/icons in dist (was deleting).
- deploy.yml uploads + byte-verifies index.html, manifest, sw.js, and all three
  icons to WebDAV.

Tests:
- tests/lib/sw-policy.test.js: 5 tests for the sandbox allow/deny logic
  (in-scope allowed; cross-origin, out-of-scope, scheme/port mismatch denied).
- Verified in headless Chromium against the built dist: SW registers + is
  active, and both a cross-origin fetch and a same-origin-out-of-scope fetch
  are refused by the CSP before leaving the device.

Fixup: dropped 'frame-ancestors' from the meta CSP after confirming the browser
ignores it there (it is a header-only directive).
2026-09-08 13:44:24 +00:00
2026-05-11 22:32:05 +00:00
2026-05-12 17:22:52 +00:00
2026-08-27 01:12:45 +00:00
2026-05-11 22:32:05 +00:00
2026-05-12 17:22:52 +00:00
2026-05-12 17:22:52 +00:00
2026-05-18 02:23:31 +00:00
2026-05-11 22:32:05 +00:00

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.

NOTE: This is majority vibe-coded with pi.dev and Qwen 3.6.

Features

  • AES-256-GCM encryption — All credentials encrypted with a key derived from your master password via PBKDF2 (600,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 — One-click random password generation in the entry form (🎲 button). Uses Web Crypto API for cryptographically secure randomness.
  • Copy to clipboard — One-click copy with 15-second auto-clear.
  • JSON import/export — Export your vault as encrypted JSON with selective group filtering. Import with merge or replace mode.
  • Auto-lock — Vault locks automatically on tab switch, visibility change, or 5-minute inactivity timer.
  • Dark theme — Responsive layout that works on desktop and mobile.

Development

Quick Start

npm install
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 for JS/CSS inlining, plus a post-build script that inlines the favicon SVG and removes leftover asset files.

Encryption Flow

Master Password ──PBKDF2──→ 256-bit Key ──AES-GCM──→ Encrypted Credential
                  (600k 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, encryptedPassword (encrypted), url, notes, groupId, tags, createdAt, updatedAt
groups id, name, color, createdAt
meta salt, testEncrypted, testPlaintext, dbVersion

Security Considerations

Threat Mitigation
Key persistence Key stored only in $state, cleared on lock/close
Weak passwords 16-character default with mixed character types
Clipboard leakage Auto-clear after 15 seconds
Tab left open Auto-lock on visibility change (tab switch)
Database tampering Passwords encrypted at rest with AES-256-GCM
Brute force PBKDF2 with 600,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 — Only encryptedPassword is encrypted at rest. Titles, usernames, URLs, and notes are stored in plaintext for searchability and are visible if the database is inspected.
  • Test plaintext stored in IndexedDB — The testPlaintext value used for password verification is stored unencrypted in the meta store. An attacker with access to IndexedDB could use it to verify guessed passwords alongside the salt.
  • No automatic backups — Use the JSON export feature to back up your vault regularly.

Development

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

Description
No description provided
Readme 2.7 MiB
Languages
JavaScript 57%
Svelte 40.2%
CSS 2%
HTML 0.8%