1a3d936afbab

say plainly at the point of removal that revocation stops future changes but recovery words survive it, and stop the monotonic entry clock leaking between tests (v2.4.6)

time
2026-07-26 08:42 UTC · 3 months ago
machine
koh2
agent
claude-opus-5
session
sync campaign remaining items
parent
2ce28477fa2c
5 files VERSION+1 −1app/build.gradle+2 −2app/src/main/java/com/peachpasswords/ui/settings/SettingsCardBodies.kt+25 −4app/src/main/java/com/peachpasswords/vault/VaultCrypto.kt+13 −0app/src/test/java/com/peachpasswords/vault/VaultCryptoTest.kt+8 −1
VERSION+1 −1
@@ -1 +1 @@1-2.4.51+2.4.6

view file

app/build.gradle+2 −2
@@ -16,8 +16,8 @@1616         applicationId "com.peachpasswords"1717         minSdk 261818         targetSdk 3519-        versionCode 24000520-        versionName "2.4.5"19+        versionCode 24000620+        versionName "2.4.6"2121 2222         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"2323         vectorDrawables {

view file

app/src/main/java/com/peachpasswords/ui/settings/SettingsCardBodies.kt+25 −4
@@ -720,13 +720,34 @@720720                         modifier = Modifier.padding(top = 2.dp),721721                     )722722                 }723-                if (confirmUnpair == d.deviceId) {724-                    Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {723+                if (confirmUnpair != d.deviceId) {724+                    SubtleButton("remove", fg) { confirmUnpair = d.deviceId }725+                }726+            }727+            // HOLE 4: revocation stops this device receiving future changes. It728+            // does NOT secure the vault against it — that device may already729+            // hold the recovery words, which are master-password-equivalent and730+            // restore the whole vault anywhere, forever, with no sync involved.731+            // Say so here rather than leaving the user falsely reassured.732+            if (confirmUnpair == d.deviceId) {733+                Column(Modifier.fillMaxWidth().padding(bottom = PeachSpacing.sm)) {734+                    Text(735+                        "Stops this device receiving future changes. It doesn't lock it " +736+                            "out of what it already has - if it's lost or stolen, also " +737+                            "change your master password and generate new recovery words.",738+                        color = fg.copy(alpha = 0.7f),739+                        fontFamily = PeachType.Body,740+                        fontSize = 11.sp,741+                        lineHeight = 16.sp,742+                    )743+                    Row(744+                        modifier = Modifier.fillMaxWidth().padding(top = PeachSpacing.sm),745+                        horizontalArrangement = Arrangement.End,746+                    ) {725747                         SubtleButton("cancel", fg) { confirmUnpair = null }748+                        Spacer(Modifier.width(PeachSpacing.sm))726749                         SubtleButton("remove", peachColors.Breach) { syncVm.unpairDevice(d.deviceId); confirmUnpair = null }727750                     }728-                } else {729-                    SubtleButton("remove", fg) { confirmUnpair = d.deviceId }730751                 }731752             }732753         }

view file

app/src/main/java/com/peachpasswords/vault/VaultCrypto.kt+13 −0
@@ -144,6 +144,19 @@144144         return base145145     }146146 147+    /**148+     * Reset the process-global monotonic entry clock. TEST ONLY.149+     *150+     * [monotonicEntryTimestamp] never moves backwards, so any test that feeds151+     * it a far-future baseline leaves every later test in the same JVM issuing152+     * far-future timestamps. That is invisible until something compares against153+     * a clock-skew bound — [SyncLimits.clampSyncTimestamp] does, and a local154+     * entry stamped beyond the skew window silently strands remote tombstones.155+     */156+    fun resetMonotonicClockForTest() {157+        _lastTimestamp = 0L158+    }159+147160     fun zero(bytes: ByteArray) {148161         bytes.fill(0)149162     }

view file

app/src/test/java/com/peachpasswords/vault/VaultCryptoTest.kt+8 −1
@@ -174,7 +174,14 @@174174 175175     @Test fun monotonic_timestamp_beats_supplied_baseline() {176176         val future = System.currentTimeMillis() + 1_000_000177-        assertTrue(VaultCrypto.monotonicEntryTimestamp(future) > future)177+        try {178+            assertTrue(VaultCrypto.monotonicEntryTimestamp(future) > future)179+        } finally {180+            // The counter is process-global and never moves backwards, so181+            // leaving it ~17 minutes ahead makes every later test in this JVM182+            // stamp entries beyond the sync clock-skew window.183+            VaultCrypto.resetMonotonicClockForTest()184+        }178185     }179186 180187     @Test fun generate_salt_and_keys_have_expected_sizes() {

view file