a74b757cd7ad

fix: preserve terminal revocations against peer floods and retain local revoke capacity (PB-26-110, v2.14.87)

time
2026-09-01 22:24 UTC · 1 month ago
machine
koh2
agent
claude-opus-5
session
2026-09-01 security assessment remediation
parent
0569c12b8b6c
2 files VERSION+1 −1app/src/main/java/com/peachpasswords/sync/PairedDeviceStore.kt+15 −5
VERSION+1 −1
@@ -1 +1 @@1-2.14.86\ No newline at end of file1+2.14.87\ No newline at end of file

view file

app/src/main/java/com/peachpasswords/sync/PairedDeviceStore.kt+15 −5
@@ -12,6 +12,8 @@1212 import javax.inject.Inject1313 import javax.inject.Singleton1414 15+private const val MAX_REVOCATION_RECORDS = 50016+1517 /**1618  * Persistent registry of paired devices, encrypted at rest with1719  * EncryptedSharedPreferences (same construction as DuressConfigStore /@@ -98,7 +100,8 @@98100         val currentRevocations = listRevocations()99101         val knownRevokedIds = currentRevocations.map { it.deviceId }.toSet()100102         val freshRevocations = batch.incomingRevocations.filterNot { knownRevokedIds.contains(it.deviceId) }101-        val nextRevocations = (currentRevocations + freshRevocations).takeLast(MAX_REVOCATIONS)103+        val admittedRevocations = freshRevocations.take((MAX_REVOCATIONS - currentRevocations.size).coerceAtLeast(0))104+        val nextRevocations = currentRevocations + admittedRevocations102105         val removed = batch.removals103106         val nextDevices = currentDevices104107             .filterNot { removed.contains(it.deviceId) }@@ -169,8 +172,9 @@169172         val known = existing.map { it.deviceId }.toSet()170173         val fresh = incoming.filterNot { known.contains(it.deviceId) }171174         if (fresh.isEmpty()) return emptyList()172-        persistRevocations((existing + fresh).takeLast(MAX_REVOCATIONS))173-        return fresh.map { it.deviceId }175+        val admitted = fresh.take((MAX_REVOCATIONS - existing.size).coerceAtLeast(0))176+        if (admitted.isNotEmpty()) persistRevocations(existing + admitted)177+        return admitted.map { it.deviceId }174178     }175179 176180     /**@@ -266,7 +270,7 @@266270         const val KEY_DEVICES = "paired_devices"267271         const val KEY_REVOCATIONS = "device_revocations"268272         /** Bound the list so a hostile peer cannot grow storage without limit. */269-        const val MAX_REVOCATIONS = 500273+        const val MAX_REVOCATIONS = MAX_REVOCATION_RECORDS270274         val json = Json { encodeDefaults = true; ignoreUnknownKeys = true }271275     }272276 }@@ -314,7 +318,13 @@314318         revokedAt = nowMs,315319         revokedByDeviceId = revokedByDeviceId,316320     )317-    return DeviceRevocationMutation(remaining, revocations + record, record)321+    val retained = if (revocations.size < MAX_REVOCATIONS) {322+        revocations323+    } else {324+        val removable = revocations.indexOfFirst { it.revokedByDeviceId != revokedByDeviceId }325+        if (removable >= 0) revocations.toMutableList().also { it.removeAt(removable) } else revocations326+    }327+    return DeviceRevocationMutation(remaining, (retained + record).takeLast(MAX_REVOCATIONS), record)318328 }319329 320330 /**

view file