45 Commits

Author SHA1 Message Date
179bc83bfe Add PWA support with a network-sandbox for the installed app
All checks were successful
Test, Build & Deploy / test-and-build (push) Successful in 55s
Test, Build & Deploy / deploy (push) Successful in 26s
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
a722c66e5a Trigger CI after runner re-registration
All checks were successful
Test, Build & Deploy / test-and-build (push) Successful in 51s
Test, Build & Deploy / deploy (push) Successful in 25s
2026-08-30 20:59:28 +00:00
be756d5fc4 Trigger CI after runner URL fix
Some checks failed
Test, Build & Deploy / test-and-build (push) Failing after 38s
Test, Build & Deploy / deploy (push) Has been skipped
2026-08-30 20:51:58 +00:00
abc13df7f9 Drop npm cache in CI (Docker-mode runner cache server causes failures)
Some checks failed
Test, Build & Deploy / test-and-build (push) Failing after 34s
Test, Build & Deploy / deploy (push) Has been skipped
2026-08-30 20:43:12 +00:00
b8f7283eb3 Switch CI runner to Ubuntu (containerized act_runner)
Some checks failed
Test, Build & Deploy / test-and-build (push) Failing after 48s
Test, Build & Deploy / deploy (push) Has been skipped
2026-08-30 20:33:39 +00:00
8652dab880 Bump vitest testTimeout to 30s for slow PBKDF2 (600k iters) derivations
All checks were successful
Test, Build & Deploy / test-and-build (push) Successful in 1m36s
Test, Build & Deploy / deploy (push) Successful in 20s
The crypto suite's deriveKey tests exceeded vitest's default 5s per-test
timeout in CI (passed locally only because the host is faster). Set a 30s
per-test timeout so the PBKDF2-heavy tests don't flake the pipeline.
2026-08-30 19:15:06 +00:00
aa651568af Add CI workflow + baked commit hash
Some checks failed
Test, Build & Deploy / test-and-build (push) Failing after 1m15s
Test, Build & Deploy / deploy (push) Has been skipped
- .gitea/workflows/deploy.yml: test+build on every push; auto-deploy
  dist/index.html to /password_manager via WebDAV on push to main (DELETE-
  then-PUT to bypass stale-file cache), verifying deployed bytes match.
- Build injects __VAULT_COMMIT__ (from VITE_COMMIT_HASH=github.sha in CI,
  git HEAD locally) and main.js logs console.info({ commit_hash }) on
  startup so a deploy is verifiable against its source commit.
2026-08-30 19:12:19 +00:00
d72f41418c Make the lock-screen warning persist based on the stored setting
- Visibility is now driven entirely by the persisted dismissedLocalWarning
  setting, loaded directly on mount (via getSetting) so the banner reflects the
  stored choice immediately when the lock screen renders — before any unlock.
- Removed the ephemeral default-true local flag that caused the banner to
  reappear on reload until the user unlocked. A warningReady guard prevents a
  one-frame flash while the value loads.
2026-08-27 01:33:45 +00:00
ae23b47a92 Validate TOTP secret in the edit dialog
- totp.js: add validateTotpSecret(input, { minBytes = 10 }). Secret is
  optional (blank ok); when provided it must decode to a base32 key of at
  least 80 bits, rejecting bad characters, typos, wrong format, and
  too-short secrets.
- EntryForm: validate the TOTP secret on submit; if invalid, show an inline
  error (⚠) and a red input border and block saving. Error clears as the
  user types.
- Tests: blank ok, valid bare/grouped/otpauth URIs, invalid chars, too-short,
  URI without a secret. 163 total pass.
2026-08-27 01:29:33 +00:00
982567acf9 Add setting to re-enable the lock-screen web-server warning
- Settings dialog gains a 'Show web-server warning' toggle that maps to the
  persisted dismissedLocalWarning flag, letting the user re-show the banner
  after dismissing it (or hide it proactively).
2026-08-27 01:22:06 +00:00
59d903fbd7 Make the lock-screen web-server warning dismissible
- The security banner (shown when opened over http(s), not file://) now has a
  close button.
- Dismissal persists via the settings store (dismissedLocalWarning), so it
  stays hidden across reloads on this browser, while still appearing fresh by
  default.
- Rephrased text kept; banner made flex with a hoverable, accessible close
  button.
2026-08-27 01:18:28 +00:00
c9a42c4670 Document TOTP support in AGENTS.md 2026-08-27 01:12:45 +00:00
9758e80a02 Add TOTP (2FA) support to entries
- New src/lib/crypto/totp.js: native RFC 6238 TOTP using Web Crypto HMAC-SHA1
  (no external crypto). base32Decode, extractSecret (bare base32 or otpauth://
  URI), generateTotp, totpRemainingSeconds.
- Entries gain an optional encryptedTotpSecret, stored AES-GCM-encrypted like
  passwords. schema.js createEntry/updateEntry/docs updated.
- Export/import re-key the TOTP secret alongside passwords when sealing with a
  separate password, and decrypt/re-encrypt on import so TOTP survives moves.
- EntryForm: optional 'TOTP Secret (2FA)' field (base32 or otpauth:// URI).
- EntryDetail: live 6-digit TOTP display updating every second with a countdown
  and urgency indicator, plus copy; guarded cleanup timer on unmount.
- Tests: RFC 6238 SHA-1 vectors (6 & 8 digit), base32/extractSecret, remaining
  seconds, schema round-trip. 157 total pass.
2026-08-27 01:11:48 +00:00
b8e7ce75f7 Make passwords optional for entries
- validateEntry no longer requires encryptedPassword; only title is required
  (schema.js). encryptedPassword documented as optional.
- EntryForm: decrypt-guards empty password on edit; stores empty string (no
  encryption) when the password field is blank; label is 'Password' (no *).
- EntryDetail: only renders the Password field when the entry has one;
  decrypt guards empty.
- Tests: schema validation updated (password optional); 143 total pass.
2026-08-27 01:07:18 +00:00
a89c7811e1 Password export: explicit choice (new password vs reuse existing vault password)
- exportSelected(groupIds, { vaultKey, password, useExistingPassword }) replaces the
  positional (groupIds, vaultKey, exportPassword) form. Protection is now an explicit
  choice, never an ambiguous optional field.
  - password mode: re-key entries under a fresh export-derived key (unchanged semantics).
  - reuseExistingPassword mode: seal with the vault's own key, keep vault salt embedded,
    so import derives the key from the vault master password. No second password required.
- ImportExport.svelte: replaces the long-placeholder free-text field with two radio
  options (Use a new password / Reuse my vault password); short placeholder, with
  client-side validation that a new password isn't empty.
- Cryptography importAll unchanged: derives the envelope key from the supplied password +
  embedded salt, which covers both sealed modes; wrong password still rejects import.
- Tests: updated call sites to options object; added round-trip + wrong-password tests for
  reuseExistingPassword.
2026-08-27 00:01:59 +00:00
800feb1d37 Export can be sealed with a separate password; import accepts file with a different password
- exportSelected(groupIds, vaultKey, exportPassword=''): plain JSON export unchanged when
  no export password; when one is set, re-keys each entry's password to a key derived from
  the export password and AES-256-GCM-seals the entire payload (titles/usernames/notes
  protected too). Returns a { format: 'encrypted-export', salt, data } envelope.
- importAll() detects sealed exports and treats the supplied password as the EXPORT password,
  so it may differ from any vault's master password. Wrong password rejects import instead of
  silently skipping entries.
- ImportExport.svelte: optional 'separate password' field in the export dialog; import dialog's
  field reworded as a generic file password covering both plain and sealed files.
- Tests for sealed export/import round-trip incl. wrong-password & missing-password rejects.
2026-08-26 23:38:25 +00:00
a71d5c4658 Don't show notes in the entry list, show a popup on hover instead. 2026-05-18 23:29:02 +00:00
d792726902 Add "use this offline" message 2026-05-18 02:40:26 +00:00
5ade18171c Add clanker-disclaimer 2026-05-18 02:23:31 +00:00
dc7c29b7ce Don't forget about ungrouped entries when selecting export groups 2026-05-18 02:11:05 +00:00
fb8df00e91 Add tests and trim down inconsistencies in the code. 2026-05-17 22:08:36 +00:00
5a240b081d Add lock settings. 2026-05-17 17:46:07 +00:00
9d9e599c09 Fix passwords not being random 2026-05-16 23:32:25 +00:00
46c49655a7 Add "Trash" 2026-05-16 23:21:53 +00:00
47609c9e7c Show notes and urls 2026-05-16 00:53:44 +00:00
7d9d3aef0e Add dragdrop grouping 2026-05-16 00:25:49 +00:00
a6589fb1f3 Fix search not working 2026-05-15 23:53:03 +00:00
f229bb978e Autofocus more forms 2026-05-15 23:53:03 +00:00
7a930228fc Make username optional. Sometimes we just want to store passwords 2026-05-15 21:19:55 +00:00
84f861a06a Autofocus the lockscreen 2026-05-15 21:11:24 +00:00
c0231fcd26 Add the AGENTs file to cut down on model code analysis 2026-05-15 21:07:09 +00:00
87aed17092 Minor things that Gemma found with Qwen's code 2026-05-15 03:34:22 +00:00
0fdcaf4ecc Fix inaccurate claims. 2026-05-12 23:49:22 +00:00
7655229aef We don't need the module loader. 2026-05-12 23:24:20 +00:00
07fefc47bb I'll just browse the repo, thanks 2026-05-12 23:08:28 +00:00
5233e165ac It was not my choice you lying clanker! 2026-05-12 23:07:58 +00:00
b5f0a13c01 Don't minify the code. We're not trying to save bandwidth. 2026-05-12 23:04:53 +00:00
49b3994de4 Import doesn't assume the same password. 2026-05-12 20:34:54 +00:00
289e8c9e34 Import should refresh the password list. 2026-05-12 18:29:47 +00:00
0c908e8919 Only output single file on build 2026-05-12 17:31:35 +00:00
b3697fa61f Single file output. 2026-05-12 17:22:52 +00:00
3f5cd5825e Fix more versioning event handler bugs 2026-05-12 17:08:59 +00:00
3e0c212c36 Fix some bugs. 2026-05-12 16:59:58 +00:00
dd9c95f72d Added tests 2026-05-11 23:35:52 +00:00
742ba505c6 First pass 2026-05-11 22:32:05 +00:00