3f8929c5bbd2

fix: resolve assessment PEACH-AND-REPORT-001 open items, gate green (v2.10.20)

PEACH-AND-01-003 FIXED: SecureFlagEffect() added to EntryDetailScreen and EntryEditScreen — the two secret-bearing screens the coverage test named. Release was already covered window-wide; this closes the debug exposure and restores the regression guardrail. PEACH-AND-01-001 FIXED: release gate green again. - Lint NewApi at PasskeyCallerOrigin.kt:52 resolved with a fail-closed Build.VERSION.SDK_INT < 28 guard inside resolve(); passkey callers remain API-34-gated, no annotation cascade. - SyncOutcomeTest: VAULT_LOCKED encoded as warning (intentional since v2.9.35; confirmed against SyncOutcomeReason.kt). - SettingsStackTest: 17dp deck reservation (gap constant 6dp -> 1dp). - SecureFlagCoverageTest centralization test now asserts the current SecureWindowPolicy API (setUserSecure/acquire/release/leases/clearFlags). Gate: 924 tests / 0 failures / 0 errors, lint 0 errors, assembleRelease OK. PEACH-AND-01-002: formal written risk acceptance for the intentional autofill posture (origin verification removed in v2.10.7; picker tap is the trust boundary) recorded in docs/security-findings-verification-2026-08-03.md with owner, rationale, residual risk, and review trigger. Report upgraded to PEACH-AND-REPORT-001 v1.1.0 (Phase-H retest addendum, evidence E-AND-0018/0019).

time
2026-08-04 04:33 UTC · 2 months ago
machine
koh2
parent
e3cf8dbbbba2
7 files VERSION+1 −1app/src/main/java/com/peachpasswords/passkeys/PasskeyCallerOrigin.kt+5 −0app/src/main/java/com/peachpasswords/ui/vault/EntryDetailScreen.kt+3 −0app/src/main/java/com/peachpasswords/ui/vault/EntryEditScreen.kt+3 −0app/src/test/java/com/peachpasswords/security/SecureFlagCoverageTest.kt+3 −2app/src/test/java/com/peachpasswords/sync/SyncOutcomeTest.kt+4 −3app/src/test/java/com/peachpasswords/ui/settings/SettingsStackTest.kt+6 −6
VERSION+1 −1
@@ -1 +1 @@1-2.10.191+2.10.20

view file

app/src/main/java/com/peachpasswords/passkeys/PasskeyCallerOrigin.kt+5 −0
@@ -1,6 +1,7 @@11 package com.peachpasswords.passkeys22 33 import android.content.Context4+import android.os.Build45 import androidx.credentials.provider.CallingAppInfo56 import com.peachpasswords.R67 import com.peachpasswords.shared.extractDomain@@ -49,6 +50,10 @@4950     fun resolve(callingAppInfo: CallingAppInfo, privilegedAllowlist: String): String? {5051         val webOrigin = runCatching { callingAppInfo.getOrigin(privilegedAllowlist) }.getOrNull()5152         if (!webOrigin.isNullOrBlank()) return webOrigin53+        // SigningInfo.getApkContentsSigners requires API 28 (minSdk 26). Passkey54+        // callers only exist on 34+, but fail closed — never crash — if this is55+        // ever reached on an older platform.56+        if (Build.VERSION.SDK_INT < 28) return null5257         val signer = runCatching { callingAppInfo.signingInfo.apkContentsSigners.firstOrNull() }.getOrNull()5358         return signer?.let { apkKeyHashOrigin(it.toByteArray()) }5459     }

view file

app/src/main/java/com/peachpasswords/ui/vault/EntryDetailScreen.kt+3 −0
@@ -83,6 +83,7 @@8383 import com.peachpasswords.ui.components.PeachEmptyOrEndState8484 import com.peachpasswords.ui.components.PeachMark8585 import com.peachpasswords.ui.components.PeachTopBar86+import com.peachpasswords.ui.components.SecureFlagEffect8687 import com.peachpasswords.ui.components.TOTPRing8788 import com.peachpasswords.ui.lock.Biometrics8889 import com.peachpasswords.ui.theme.LocalPeachAccent@@ -110,6 +111,8 @@110111     modifier: Modifier = Modifier,111112     vm: EntryDetailViewModel = hiltViewModel(),112113 ) {114+    SecureFlagEffect()115+113116     val entry by vm.entry.collectAsState()114117     val activeEntries by vm.activeEntries.collectAsState()115118     val identitySuggestions by vm.identitySuggestions.collectAsState()

view file

app/src/main/java/com/peachpasswords/ui/vault/EntryEditScreen.kt+3 −0
@@ -47,6 +47,7 @@4747 import com.peachpasswords.ui.components.PeachEmptyOrEndState4848 import com.peachpasswords.ui.components.PeachSegment4949 import com.peachpasswords.ui.components.PeachSegmentedControl50+import com.peachpasswords.ui.components.SecureFlagEffect5051 import com.peachpasswords.ui.components.PeachTopBar5152 import com.peachpasswords.ui.theme.LocalPeachAccent5253 import com.peachpasswords.ui.theme.PeachMotion@@ -63,6 +64,8 @@6364     onOpenNote: (String) -> Unit = {},6465     vm: EntryEditViewModel = hiltViewModel(),6566 ) {67+    SecureFlagEffect()68+6669     val f by vm.form.collectAsState()6770     val usernameSuggestions by vm.usernameSuggestions.collectAsState()6871     val emailSuggestions by vm.emailSuggestions.collectAsState()

view file

app/src/test/java/com/peachpasswords/security/SecureFlagCoverageTest.kt+3 −2
@@ -75,9 +75,10 @@7575             "../app/src/main/java/com/peachpasswords/ui/components/SecureWindowPolicy.kt",7676         )7777         val source = helper.readText()78-        assertTrue(source.contains("installRelease"))78+        assertTrue(source.contains("setUserSecure"))79+        assertTrue(source.contains("acquire"))80+        assertTrue(source.contains("release"))7981         assertTrue(source.contains("leases"))80-        assertTrue(source.contains("releaseOwned"))8182         assertTrue(source.contains("clearFlags"))8283         // MainActivity and secret activities delegate to the owner; they must8384         // not carry a second ad-hoc clear/set lifecycle that can drift.

view file

app/src/test/java/com/peachpasswords/sync/SyncOutcomeTest.kt+4 −3
@@ -38,14 +38,14 @@3838      * Network-class outcomes (relay/handshake/timeout/connection_lost) live in3939      * [SyncOutcomeReason.isWarning] and resolve to the amber light, not red —4040      * the background loop keeps trying, so a transient miss is a warning, not41-     * a problem.41+     * a problem. VAULT_LOCKED joined them in v2.9.35 for the same reason: the42+     * loop retries once the vault unlocks, so amber, not red.4243      */4344     @Test fun `the failing outcomes are exactly these`() {4445         assertEquals(4546             setOf(4647                 SyncOutcomeReason.NO_ELIGIBLE_HOST,4748                 SyncOutcomeReason.PRIVATE_RELAY_UNSET,48-                SyncOutcomeReason.VAULT_LOCKED,4949                 // Android refused to start the runner: nothing synced, and the5050                 // person can do something about it.5151                 SyncOutcomeReason.FOREGROUND_START_BLOCKED,@@ -56,9 +56,10 @@5656         )5757     }5858 59-    @Test fun `the warning outcomes are exactly the network class`() {59+    @Test fun `the warning outcomes are the network class plus a locked vault`() {6060         assertEquals(6161             setOf(62+                SyncOutcomeReason.VAULT_LOCKED,6263                 SyncOutcomeReason.RELAY_UNREACHABLE,6364                 SyncOutcomeReason.HANDSHAKE_FAILED,6465                 SyncOutcomeReason.SYNC_TIMEOUT,

view file

app/src/test/java/com/peachpasswords/ui/settings/SettingsStackTest.kt+6 −6
@@ -27,15 +27,15 @@2727 2828     @Test fun settings_deck_fills_down_to_just_above_the_build_label() {2929         // Reserves the label's nav-bar inset (passed in) + its padding/line height30-        // (xs 4dp + 12dp = 16dp) + a 6dp gap = navBarBottom + 22dp. No hard cap, so30+        // (xs 4dp + 12dp = 16dp) + a 1dp gap = navBarBottom + 17dp. No hard cap, so3131         // tall panels (Galaxy Z Fold 7) fill to the label instead of stranding dead3232         // space below the stack and clipping the Pro card's copy/reveal buttons.33-        assertEquals(878.dp, settingsDeckHeight(900.dp, 0.dp))34-        assertEquals(458.dp, settingsDeckHeight(480.dp, 0.dp))35-        assertEquals(2.dp, settingsDeckHeight(24.dp, 0.dp))36-        assertEquals(1178.dp, settingsDeckHeight(1200.dp, 0.dp))33+        assertEquals(883.dp, settingsDeckHeight(900.dp, 0.dp))34+        assertEquals(463.dp, settingsDeckHeight(480.dp, 0.dp))35+        assertEquals(7.dp, settingsDeckHeight(24.dp, 0.dp))36+        assertEquals(1183.dp, settingsDeckHeight(1200.dp, 0.dp))3737         // A larger nav-bar inset (three-button nav) simply reserves more at the bottom.38-        assertEquals(830.dp, settingsDeckHeight(900.dp, 48.dp))38+        assertEquals(835.dp, settingsDeckHeight(900.dp, 48.dp))3939     }4040 4141     // ── Drag gesture resolution ───────────────────────────────────────────────

view file