fix(quartz): stop a stray edition from pinning a Control entity forever

B1 in docs/concord-soft-ban-audit.md, the worst item on the list: one edition at
version = Long.MAX_VALUE permanently pinned its entity to the author's content,
for every client holding a floor for it, with no way back. The floor rose to
MAX_VALUE, no honest edition could exceed it, and a Refounding that dropped the
poison fell back to EntityFloor.known — the poison. Authored in the tests by a
current, legitimately granted moderator: no ban, no sockpuppet, one ordinary
permission bit.

The chain walk was never the weakness; it advances only to head.version + 1
citing the head's hash, so a fresh joiner was untouched. The compaction arm was:
it trades contiguity for cross-epoch tolerance, which left VERSION as the only
contest an edition had to win.

Two changes. The arm now tries the floor-anchored chain first and falls back to
the raw-version bootstrap only when nothing connects, so a stray never wins a
fold where the honest chain is present. And the bootstrap will not follow a jump
of more than MAX_COMPACTION_VERSION_JUMP above the floor — a compacted head is
legitimately ahead by a chain's worth, not by 2^63 — so the version space cannot
be exhausted in a step. A new test pins the tolerance the arm exists for, so the
bound cannot later be tightened into breaking CORD-06 §3.

compactControlPlane picked its per-entity head by raw highest version too, which
made an honest rotator the delivery mechanism: a disconnected stray never joins
the chain but won that comparison, and was re-wrapped into the new epoch as the
entity's whole history, where fresh joiners anchor on it. It now picks the chain
head, keeping foldEntity's fresh-joiner fallback for the dangling `prev` a prior
compaction leaves behind.

The three reproductions now assert the fixed behaviour. The banlist's escape
hatch (a floor-less chain walk plus the re-heal union) is kept and still pinned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DrJhpFhhLjuDJQNkGvYMGj
This commit is contained in:
Claude
2026-08-09 15:12:41 +00:00
parent 3a53292993
commit 54c412da7a
3 changed files with 176 additions and 48 deletions
@@ -150,9 +150,63 @@ object EditionFold {
floorVersion: Long,
): ControlEdition? =
editions
.filter { it.version >= floorVersion }
.filter { it.version >= floorVersion && it.version - floorVersion <= MAX_COMPACTION_VERSION_JUMP }
.minWithOrNull(compareByDescending<ControlEdition> { it.version }.thenBy { it.rumorId })
/**
* How far above the floor the compaction arm will follow an edition in one step.
*
* The arm trades contiguity for cross-epoch tolerance, which made VERSION the only contest an
* edition had to win — so a single authorized edition at `version = Long.MAX_VALUE` used to
* become an entity's permanent head: it won the arm, `authorizedHeads` raised the floor to
* `Long.MAX_VALUE`, and from there no honest edition could ever exceed the floor again. Even a
* Refounding that dropped the poison did not help, because nothing was then offered at or above
* the floor and the fold fell back to [EntityFloor.known] — the poison itself. See B1 in
* `docs/concord-soft-ban-audit.md`.
*
* A compacted head is legitimately ahead of the floor by however many editions the entity gained
* while we were away — a chain's worth, not 2^63. This bound is deliberately far above any real
* community (a channel renamed a thousand times a day for three years stays under it) and far
* below the point where the version space can be exhausted. Anything beyond it is not a
* compaction we missed; it is someone reaching for the ceiling, and it is treated as a gap.
*/
const val MAX_COMPACTION_VERSION_JUMP = 1_000_000L
/**
* The floor-anchored chain head among [editions], or null when nothing connects to [floor].
*
* The same anchor-then-walk the main path uses, factored out so the compaction arm can try it
* first: the anchor is the floor's own edition (same version AND hash) or its immediate
* successor citing that hash, then the walk climbs while each `version + 1` cites the current
* head. Reports no gap — a null here means "fall back", not "refuse".
*/
private fun chainHead(
editions: List<ControlEdition>,
floor: EntityFloor,
): ControlEdition? {
val byVersion = HashMap<Long, MutableList<ControlEdition>>()
for (e in editions) byVersion.getOrPut(e.version) { ArrayList() }.add(e)
val lowest = byVersion.keys.filter { it >= floor.version }.minOrNull() ?: return null
val winner = byVersion[lowest]?.minByOrNull { it.rumorId } ?: return null
var head =
when (lowest) {
floor.version -> winner.takeIf { it.hashHex == floor.hashHex }
floor.version + 1 -> winner.takeIf { it.prevHash != null && it.prevHash.toHexKey() == floor.hashHex }
else -> null
} ?: return null
while (true) {
val next =
byVersion[head.version + 1]
?.filter { it.prevHash != null && it.prevHash.toHexKey() == head.hashHex }
?.minByOrNull { it.rumorId }
?: break
head = next
}
return head
}
/**
* Groups mixed [editions] by entity id and folds each to its head, honoring the
* per-entity anti-rollback [floors] (keyed by [ControlEdition.entityIdHex]).
@@ -210,10 +264,16 @@ object EditionFold {
// to the compacted head. Presence of the entity in the snapshot selects the ARM;
// version selects the HEAD, over every edition we hold and not just the subset.
if (floor != null && snapshot != null && editions.any { it.rumorId in snapshot }) {
// Chain first, bootstrap only as the fallback. The arm exists for the case where the
// offered head genuinely cannot be connected — but when it CAN be, the connected head is
// strictly better evidence than "highest number wins", and preferring it denies a stray
// high-version edition its free win in every ordinary fold. The bootstrap keeps the
// cross-epoch case working, now bounded by MAX_COMPACTION_VERSION_JUMP.
chainHead(editions, floor)?.let { return it }
return bootstrapHead(editions, floor.version)
?: run {
// Nothing at or above the floor was served: the head we already accepted
// vanished from the offered set — withheld, so fail closed.
// Nothing admissible at or above the floor was served: the head we already
// accepted vanished from the offered set — withheld, so fail closed.
onGap(editions[0].entityIdHex, floor.version, editions.maxOf { it.version })
floor.known
}
@@ -21,6 +21,7 @@
package com.vitorpamplona.quartz.concord.cord06Rekey
import com.vitorpamplona.quartz.concord.cord04Roles.ControlEdition
import com.vitorpamplona.quartz.concord.cord04Roles.EditionFold
import com.vitorpamplona.quartz.concord.crypto.ConcordKeyDerivation
import com.vitorpamplona.quartz.concord.crypto.ControlPlaneKeys
import com.vitorpamplona.quartz.concord.crypto.GroupKey
@@ -167,18 +168,29 @@ object ConcordRefounding {
priorControlKeys: ControlPlaneKeys,
newControlKeys: ControlPlaneKeys,
): List<Event> {
// entity coordinate -> (head edition, its verified seal)
val heads = HashMap<String, Pair<ControlEdition, Event>>()
// entity coordinate -> every edition we can open, paired with its verified seal.
val byCoordinate = HashMap<String, MutableList<Pair<ControlEdition, Event>>>()
for (wrap in priorWraps) {
val opened = ConcordStreamEnvelope.openOrNull(wrap, priorControlKeys) ?: continue
val edition = ControlEdition.fromRumor(opened.rumor) ?: continue
val coord = edition.entityKind.wire + ":" + edition.entityIdHex
val current = heads[coord]
if (current == null || edition.version > current.first.version) {
heads[coord] = edition to opened.seal
}
byCoordinate.getOrPut(coord) { ArrayList() }.add(edition to opened.seal)
}
return heads.values.map { (_, seal) -> ConcordStreamEnvelope.wrapSeal(seal, newControlKeys, createdAt = seal.createdAt) }
// The head is the CHAIN head, not the highest version. Picking by raw version made an honest
// rotator the delivery mechanism for a disconnected stray: an edition minted at an arbitrary
// version never joins the chain, but it won this comparison and was then re-wrapped into the
// new epoch as that entity's whole history — where a fresh joiner, holding no floor, anchors
// on it as their baseline. See B1 in `docs/concord-soft-ban-audit.md`. foldEntity walks from
// genesis and keeps the fresh-joiner fallback for a head whose own `prev` dangles into an
// epoch this rotator no longer holds, which is the ordinary shape after a prior compaction.
val out = ArrayList<Event>(byCoordinate.size)
for ((_, entries) in byCoordinate) {
val head = EditionFold.foldEntity(entries.map { it.first }) ?: continue
val seal = entries.firstOrNull { it.first.rumorId == head.rumorId }?.second ?: continue
out.add(ConcordStreamEnvelope.wrapSeal(seal, newControlKeys, createdAt = seal.createdAt))
}
return out
}
/**
@@ -27,35 +27,43 @@ import kotlin.test.assertEquals
import kotlin.test.assertTrue
/**
* **V1 in `docs/concord-soft-ban-audit.md` — reproduction.** A single Control Plane edition at
* `version = Long.MAX_VALUE` pins its entity to the author's content permanently, for every client
* that holds a floor for it.
* **B1 in `docs/concord-soft-ban-audit.md` — regression guard.** A single Control Plane edition at
* `version = Long.MAX_VALUE` used to pin its entity to the author's content permanently, for every
* client that held a floor for it. These tests failed before the fix and pass after it.
*
* The chain walk is not the weakness — it advances only to `head.version + 1` citing the head's
* hash, so an inflated version is unreachable and a fresh joiner is unaffected. The weakness is the
* **compaction arm** of [EditionFold.foldEntity]: once a client holds a floor for an entity and that
* entity appears in the epoch snapshot (which `ConcordCommunityState.fold` always builds from the
* editions handed to it), the head is chosen by [EditionFold.bootstrapHead] — *highest version at or
* above the floor*, with no `prev`, no hash, and no contiguity. Version is then the whole contest,
* and `Long.MAX_VALUE` wins it forever:
* The chain walk was never the weakness — it advances only to `head.version + 1` citing the head's
* hash, so an inflated version is unreachable and a fresh joiner was unaffected. The weakness was the
* **compaction arm** of `EditionFold.foldEntity`: once a client held a floor for an entity and that
* entity appeared in the epoch snapshot (which `ConcordCommunityState.fold` always builds from the
* editions handed to it), the head came from the raw-version bootstrap — *highest version at or above
* the floor*, with no `prev`, no hash, and no contiguity. Version was then the whole contest, and
* `Long.MAX_VALUE` won it forever:
*
* 1. the poison becomes the head, so the entity shows the attacker's content;
* 2. `authorizedHeads` raises the entity's floor to `Long.MAX_VALUE`;
* 3. no honest edition can ever exceed that floor, so the entity can never be repaired;
* 4. a Refounding that drops the poison does not help either — nothing is offered at or above the
* floor, so the fold reports a gap and falls back to [EntityFloor.known], which *is* the poison.
* 1. the poison became the head, so the entity showed the attacker's content;
* 2. `authorizedHeads` raised the entity's floor to `Long.MAX_VALUE`;
* 3. no honest edition could ever exceed that floor, so the entity could never be repaired;
* 4. a Refounding that dropped the poison did not help either — nothing was then offered at or above
* the floor, so the fold reported a gap and fell back to `EntityFloor.known`, which *was* the poison.
*
* Two changes close it, and both are pinned below. The arm now tries the floor-anchored **chain**
* first and only falls back to the raw-version bootstrap when nothing connects, so a stray never wins
* a fold where the honest chain is present; and the bootstrap will not follow a jump larger than
* [EditionFold.MAX_COMPACTION_VERSION_JUMP], so the version space cannot be exhausted in one step.
* [aGenuineCompactionJumpIsStillFollowed] pins the tolerance the arm exists for, so the bound cannot
* be tightened into breaking CORD-06 §3.
*
* Note who the attacker is. Every test here is authored by **bob, a current and legitimately granted
* moderator** — not a banned member, not a sockpuppet. Any holder of the entity's permission bit can
* do this at any time, and demoting or banning them afterwards changes nothing, because the damage
* is already in every client's floor. It is also carried into every future epoch by
* `ConcordRefounding.compactControlPlane`, which selects the head per entity by raw highest version.
* could do this at any time, and demoting or banning them afterwards changed nothing, because the
* damage was already in every client's floor. `ConcordRefounding.compactControlPlane` also selected
* the head per entity by raw highest version, which made an honest rotator the delivery mechanism —
* it now picks the chain head instead.
*
* The banlist is the one entity that survives, and by accident: `AuthorityResolver` folds it with
* The banlist was the one entity that survived, and by accident: `AuthorityResolver` folds it with
* its own floor-less chain walk and then re-heals the union across authorized editions, so an
* honest ban lands even when the head is poisoned. [aPoisonedBanlistStillAcceptsTheOwnersBan] pins
* that, because it is the only thing standing between this bug and a permanently unmoderatable
* community.
* honest ban landed even when the head was poisoned. [aPoisonedBanlistStillAcceptsTheOwnersBan]
* keeps pinning that, because it was the only thing standing between this bug and a permanently
* unmoderatable community.
*/
class ControlPlaneVersionExhaustionTest {
private val owner = "0f".repeat(32)
@@ -86,7 +94,7 @@ class ControlPlaneVersionExhaustionTest {
) + rest
@Test
fun oneEditionAtMaxVersionPinsTheMetadataForever() {
fun oneEditionAtMaxVersionNoLongerPinsTheMetadata() {
val metadataV0 = edition(ControlEntityKind.METADATA, metadataEntity, 0, null, """{"name":"My Community"}""", owner, "meta-0")
val community = communityWhereBobHolds(ConcordPermissions.of(ConcordPermissions.MANAGE_METADATA).toWire(), metadataV0)
@@ -96,7 +104,7 @@ class ControlPlaneVersionExhaustionTest {
val poison = edition(ControlEntityKind.METADATA, metadataEntity, Long.MAX_VALUE, metadataV0.hash, """{"name":"PWNED"}""", bob, "meta-poison")
val floorsAfter = ConcordCommunityState.authorizedHeads(community + poison, owner, floorsBefore)
assertEquals(Long.MAX_VALUE, floorsAfter[metadataEntity]?.version, "VULNERABLE: the floor is now at the top of the version space")
assertEquals(0, floorsAfter[metadataEntity]?.version, "the floor must not follow a stray to the top of the version space")
// The owner tries to repair it, chaining honestly onto their own genesis.
val repair = edition(ControlEntityKind.METADATA, metadataEntity, 1, metadataV0.hash, """{"name":"My Community"}""", owner, "meta-1")
@@ -108,19 +116,19 @@ class ControlPlaneVersionExhaustionTest {
"a fresh joiner walks the chain and is unaffected",
)
assertEquals(
"PWNED",
"My Community",
ConcordCommunityState.fold(pool, owner, floorsAfter).metadata?.name,
"VULNERABLE: every client holding a floor is pinned to the attacker's content",
"a client holding a floor follows the honest chain, not the stray",
)
assertEquals(
"PWNED",
"My Community",
ConcordCommunityState.fold(community + repair, owner, floorsAfter).metadata?.name,
"VULNERABLE: even a Refounding that drops the poison falls back to it as EntityFloor.known",
"and a Refounding that drops the poison stays repaired",
)
}
@Test
fun oneEditionAtMaxVersionDeletesAChannelForever() {
fun oneEditionAtMaxVersionNoLongerDeletesAChannel() {
val channelV0 = edition(ControlEntityKind.CHANNEL, channelEntity, 0, null, """{"name":"general"}""", owner, "chan-0")
val community = communityWhereBobHolds(ConcordPermissions.of(ConcordPermissions.MANAGE_CHANNELS).toWire(), channelV0)
@@ -132,28 +140,29 @@ class ControlPlaneVersionExhaustionTest {
val pool = community + poison + repair
assertEquals(1, ConcordCommunityState.fold(pool, owner).channels.size, "a fresh joiner still sees the channel")
assertEquals(0, ConcordCommunityState.fold(pool, owner, floorsAfter).channels.size, "VULNERABLE: the channel is gone and cannot be restored")
assertEquals(1, ConcordCommunityState.fold(pool, owner, floorsAfter).channels.size, "and so does a client holding a floor")
assertEquals(
0,
1,
ConcordCommunityState.fold(community + repair, owner, floorsAfter).channels.size,
"VULNERABLE: dropping the poison does not bring the channel back",
"the channel survives a Refounding too",
)
}
@Test
fun aPoisonedBanlistStillAcceptsTheOwnersBan() {
// The saving grace, and the reason this bug is "unmoderatable community" rather than
// "community with a broken name". AuthorityResolver folds the banlist on its own floor-less
// chain walk and re-heals the union across every authorized edition, so the owner's ban lands
// even while the banlist's own floor sits at Long.MAX_VALUE. Do not "unify" the banlist onto
// the floored fold without replacing this protection.
// This was the saving grace before the fix — the reason the bug was "community with a broken
// name" rather than "community nobody can moderate". AuthorityResolver folds the banlist on
// its own floor-less chain walk and re-heals the union across every authorized edition, so
// the owner's ban landed even while the banlist's floor sat at Long.MAX_VALUE. The floor can
// no longer be poisoned, but keep this: do not "unify" the banlist onto the floored fold
// without replacing the protection.
val banlistV0 = edition(ControlEntityKind.BANLIST, banlistEntity, 0, null, "[]", owner, "ban-0")
val community = communityWhereBobHolds(ConcordPermissions.of(ConcordPermissions.BAN).toWire(), banlistV0)
val floorsBefore = ConcordCommunityState.authorizedHeads(community, owner)
val poison = edition(ControlEntityKind.BANLIST, banlistEntity, Long.MAX_VALUE, banlistV0.hash, "[]", bob, "ban-poison")
val floorsAfter = ConcordCommunityState.authorizedHeads(community + poison, owner, floorsBefore)
assertEquals(Long.MAX_VALUE, floorsAfter[banlistEntity]?.version, "the banlist floor is poisoned like any other")
assertEquals(0, floorsAfter[banlistEntity]?.version, "the banlist floor is no longer poisonable either")
val ownerBansBob = edition(ControlEntityKind.BANLIST, banlistEntity, 1, banlistV0.hash, """["$bob"]""", owner, "ban-1")
val pool = community + poison + ownerBansBob
@@ -164,4 +173,51 @@ class ControlPlaneVersionExhaustionTest {
"the re-heal union must keep the banlist working even with a poisoned floor",
)
}
@Test
fun aGenuineCompactionJumpIsStillFollowed() {
// The tolerance the compaction arm exists for, pinned so the bound above cannot be tightened
// into breaking CORD-06 §3. After a Refounding the compacted head carries the `prev` it had
// before compaction, citing an edition in the PRIOR epoch that this client no longer holds —
// so it connects to nothing, and its version is legitimately several ahead of our floor.
val metadataV0 = edition(ControlEntityKind.METADATA, metadataEntity, 0, null, """{"name":"My Community"}""", owner, "meta-0")
val community = communityWhereBobHolds(ConcordPermissions.of(ConcordPermissions.MANAGE_METADATA).toWire(), metadataV0)
val floors = ConcordCommunityState.authorizedHeads(community, owner)
val danglingPrev = ByteArray(32) { 0x7f }
val compacted = edition(ControlEntityKind.METADATA, metadataEntity, 4, danglingPrev, """{"name":"Renamed While We Were Away"}""", owner, "meta-compacted")
assertEquals(
"Renamed While We Were Away",
ConcordCommunityState.fold(community.filter { it.entityKind != ControlEntityKind.METADATA } + compacted, owner, floors).metadata?.name,
"a compacted head whose prev dangles by design must still be adopted",
)
}
@Test
fun aJumpBeyondTheCapIsRefusedAsAGap() {
// Same shape as the genuine compaction above, one version past the bound: not a compaction we
// missed, so the fold reports a gap and keeps what it already had rather than following it.
val metadataV0 = edition(ControlEntityKind.METADATA, metadataEntity, 0, null, """{"name":"My Community"}""", owner, "meta-0")
val community = communityWhereBobHolds(ConcordPermissions.of(ConcordPermissions.MANAGE_METADATA).toWire(), metadataV0)
val floors = ConcordCommunityState.authorizedHeads(community, owner)
val danglingPrev = ByteArray(32) { 0x7f }
val tooFar =
edition(
ControlEntityKind.METADATA,
metadataEntity,
EditionFold.MAX_COMPACTION_VERSION_JUMP + 1,
danglingPrev,
"""{"name":"PWNED"}""",
bob,
"meta-far",
)
assertEquals(
"My Community",
ConcordCommunityState.fold(community.filter { it.entityKind != ControlEntityKind.METADATA } + tooFar, owner, floors).metadata?.name,
"a jump past the bound is a gap, not a head",
)
}
}