Skip to content

feat(credentials): web keys expand step E with a sealed mirror (VC-643) - #759

Merged
hussainph merged 4 commits into
mainfrom
volli/VC-643-credentials-web-keys-expand-step-e-and-reconcili
Oct 5, 2026
Merged

hussainph merged 4 commits into
mainfrom
volli/VC-643-credentials-web-keys-expand-step-e-and-reconcili

Conversation

@hussainph

@hussainph hussainph commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Additive. raisesMinReader is off and the floor is unchanged (58).
  • web_credential_source has one row: a random lineage id (lower(hex(randomblob(16)))) and a monotonic revision.
  • Three AFTER INSERT/UPDATE/DELETE ON secrets triggers advance the revision, clears included, no matter which build wrote.
  • web_credential_mirror holds the last verified receipt: source lineage, revision, inventory id and generation.

Testing

Original validation includes vp check, pnpm typecheck.

Migration 059 (additive, raisesMinReader off, floor unchanged) adds
web_credential_source (random lineage id + a revision that triggers on
secrets advance on every insert/update/delete, clears included) and
web_credential_mirror (the last verified receipt). Both are excluded from
bundles and rebuilt on open.

SealedInventory.mirror makes a family an exact copy of a canonical source
under one hold of the credential lock: snapshot in a short synchronous
SQLite read, seal, fsync, read back, open and compare, with a receipt naming
the source lineage and revision. Cleared rows are dropped, so a stale mirror
never keeps a cleared key; contents are compared, not only revisions.

WebCredentialMirror reconciles the web-search family from secrets on every
desktop launch (after first paint) and after every save/clear, which commit
to SQLite first. One synchronous attempt at the lock, async retries while
busy. Failures (locked/corrupt key material, busy, full disk, fsync) leave
sealing pending and the source untouched; the view says 'pending' (saved;
sealing pending) and the log labels legacy mode as not encrypted.

Desktop gets the keychain keyring VC-642 reserved: a random key wrapped by
safeStorage in host-credentials.key, asked for lazily once per launch.
Reads stay on SQLite (the read switch is VC-644).

Tests: migration, inventory mirror, keyring, mirror failures (crash at each
file/SQL boundary in child processes, resurrection, disk-full/fsync,
multi-process, pre-E upgrade, restore missing credentials) and N-1 runs of
main's exact code (pinned by git blob id) for save, clear, attach, bundle
and restore.
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 70acb2a3-e84e-450e-8852-de5bd3677f62
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

- Nothing to protect (no keys, no sealed file) answers 'none' before the key
  backend is asked anything; the keychain keyring's probe no longer asks
  whether the keychain is available unless a wrapped key exists.
- Desktop's boot reconcile waits 5 s after first paint (unref'd timer).
- Snapshot and status reads use a deferred read transaction, never SQLite's
  write lock, so they never wait on a writer while holding the credential lock.
- A throwing onResult listener can no longer fail a committed save or reject
  the boot reconcile.
- A wrapped key this keychain will not open, with no sealed inventory beside
  it, is set aside (never deleted) and replaced instead of sticking pending.
- Pinned N-1 copies move to '.pinned' (out of tsc, fmt and the host import
  checker) and cover every host-core file this ticket touched, comment-only
  changes included.
- Docs: sealing failures are remembered per launch; no Settings label in E
  (owner decision: it comes with VC-644).
…epted quit (VC-643)

- keychainCredentialKeyring uses safeStorage's async API only; unlock()
  fetches off the credential lock and is abortable; resolve/active answer
  from what it fetched or throw CredentialKeyPendingError (key-pending).
- Linux fails closed without getSelectedStorageBackend, on basic_text or
  unknown, and on Chromium's v10 fallback wrapping.
- The launch reconcile may unlock only if this launch already used the
  keychain; a person's save or clear always may.
- registerAcceptedQuitCoordinator gains stopBackgroundWork; desktop's
  webSealingLifecycle cancels the launch timer, busy retries and in-flight
  key fetches at accepted quit.
- reconcile() reports pending 'moved' with the sealed revision when the
  source changed during the seal.
- Preload comment no longer claims keychain encryption.
- docs/secrets.md records the async-API and unattended-access decisions.
@hussainph
hussainph merged commit ba04657 into main Oct 5, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant