diff options
| author | Danilo M. <danix@danix.xyz> | 2026-11-02 23:38:48 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-11-02 23:38:48 +0100 |
| commit | c0c98afc8abbcbac28ace330a5c8659aba0cd572 (patch) | |
| tree | be4e0d33e3ffd0b541434bcf8690a21f7a88bae4 /app/src/main | |
| parent | 660afb674b1580a1fc54f09550d9279cf96ec71c (diff) | |
| download | HardenedPass-c0c98afc8abbcbac28ace330a5c8659aba0cd572.tar.gz HardenedPass-c0c98afc8abbcbac28ace330a5c8659aba0cd572.zip | |
fix(app): harden the entry passphrase prompt
Ignore a submit while no prompt is showing, so OK plus IME Done cannot start
two decrypts. Key the passphrase dialog on the retry count so each attempt
gets a fresh field and shows the wrong password error. Note the process
death gap around key import and the editor. Record the TotpModule,
text selection and file IO decisions in the plan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'app/src/main')
| -rw-r--r-- | app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt | 20 | ||||
| -rw-r--r-- | app/src/main/java/app/passwordstore/ui/entry/EntryViewModel.kt | 6 |
2 files changed, 18 insertions, 8 deletions
diff --git a/app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt b/app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt index a54b517d..5c16b5a3 100644 --- a/app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt +++ b/app/src/main/java/app/passwordstore/ui/entry/EntryDestination.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember @@ -104,6 +105,9 @@ fun EntryDestination( } } + // ponytail: after process death during key import or the editor, the unlock flow can start + // twice or a prompt can be left over the list; rare, no secret exposed. Fix by tracking + // whether the flow already started if it shows up. val keyImport = rememberLauncherForActivityResult(StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) unlock() else onClose() @@ -173,13 +177,15 @@ fun EntryDestination( ) EntryPrompt.Passphrase, EntryPrompt.WrongPassphrase -> - PassphraseDialog( - wrong = viewModel.prompt == EntryPrompt.WrongPassphrase, - cacheEnabled = viewModel.cacheEnabled, - clearCache = viewModel.clearCache, - onSubmit = { passphrase, clear -> viewModel.submit(passphrase, clear, activity) }, - onCancel = viewModel::cancel, - ) + key(viewModel.retries) { + PassphraseDialog( + wrong = viewModel.prompt == EntryPrompt.WrongPassphrase, + cacheEnabled = viewModel.cacheEnabled, + clearCache = viewModel.clearCache, + onSubmit = { passphrase, clear -> viewModel.submit(passphrase, clear, activity) }, + onCancel = viewModel::cancel, + ) + } } } } diff --git a/app/src/main/java/app/passwordstore/ui/entry/EntryViewModel.kt b/app/src/main/java/app/passwordstore/ui/entry/EntryViewModel.kt index 38d3b9d9..5714f2a6 100644 --- a/app/src/main/java/app/passwordstore/ui/entry/EntryViewModel.kt +++ b/app/src/main/java/app/passwordstore/ui/entry/EntryViewModel.kt @@ -86,7 +86,10 @@ constructor( private var started = false private var copyOnDecrypt = false - private var retries = 0 + /** Failed-or-asked passphrase prompts so far; keys the dialog so each attempt starts fresh. */ + var retries = 0 + private set + private var auth: BiometricResult = BiometricResult.CanceledByUser private var ids: List<PGPIdentifier> = emptyList() private var file: File? = null @@ -115,6 +118,7 @@ constructor( } fun submit(passphrase: String, clearCache: Boolean, context: Context) { + if (prompt == EntryPrompt.None) return prompt = EntryPrompt.None this.clearCache = clearCache val app = context.applicationContext |
