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
↓ peach-android-a74b757c.tar.gzatlas at this save
files changed2 files
VERSION+1 −1app/src/main/java/com/peachpasswords/sync/PairedDeviceStore.kt+15 −5VERSION+1 −1
@@ -1 +1 @@1-2.14.86\ No newline at end of file1+2.14.87\ No newline at end of fileapp/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 /**