fix(server): gracefully degrade when vault is locked during browser search#93
Merged
Merged
Conversation
…earch RankingPreferences.load() and PersonalizationPreferences.load() both document themselves as fail-soft, but store.get() was unguarded and threw when the DEK was absent. On a passphrase-mode device the process lifecycle wipes the DEK on background, so a browser search while the app is backgrounded crashed with "vault is locked: DEK not present in memory". Wrap store.get() in runCatching so locked-vault reads fall back to empty defaults instead of propagating the error to the served page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAUvwoGuyk1ToQf9WSU7A1
Extract a shared locked() helper to stay under the 120-char line limit and match the project's multi-line parameter style for put(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAUvwoGuyk1ToQf9WSU7A1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
When the app is in passphrase (zero-knowledge) mode and the user switches to the browser to search, the Android process lifecycle fires
onStop(), which wipes the DEK from memory viaStorageLockController.lockNow(). The embedded web server is still running, so the browser's search request hits/searchand callsrankingPreferences.load()— which tries to decrypt preferences viastore.get()→EncryptedPreferencesCodec.decode()→dek(). With the DEK gone,DekHolder.get()throws"vault is locked: DEK not present in memory", and the unhandled error propagates to the browser as a crash page.Both
RankingPreferences.load()andPersonalizationPreferences.load()document themselves as "fail-soft" (missing or corrupt → empty defaults), but thestore.get()call was unguarded and threw on a locked vault. This wraps those calls inrunCatchingso a locked vault is treated the same as a missing value — search works with default/empty ranking rules, and personalization is simply absent, exactly as the docs describe.Relates to OpenSpec change: N/A (bug fix)
Checklist
feat//fix/branch offmain./gradlew ktlintCheck lint test assembleDebugis green locallyopenspec validate <name> --strict) and tasks are checked offGenerated by Claude Code