The finding
Observed in the walking-skeleton spike, phase 3b (spikes/skeleton/, commit 3177cbd; discussion on #8): with subduction_keyhive's SubductionKeyhive policy gating pull against the keyhive auth graph,
- explicit pulls behave correctly —
sync_with_peer from a non-member (before add, and again after revocation) is refused with an empty diff (success=false, nothing sent — the no-information-leak posture);
- but a subscription established while authorized keeps delivering after revocation — the revoked peer continued to receive new ciphertext commits over the live subscription (
OBSERVED: v3 ciphertext still pushed to the revoked subscriber in the spike run).
Mechanics: subduction reaches a peer two ways — request/response sync, which consults StoragePolicy::authorize_fetch / filter_authorized_fetch, and subscription push, which walks the subscriber list built at sync time. Revocation updates the auth graph, but nothing removes the peer from the subscriber list and the push path does not re-check the policy.
Impact is bounded by the crypto tier — BeeKEM revocation excluded the peer from new epochs, so everything pushed is undecryptable (KeyNotFound, asserted in the spike) — but what leaks is precisely what pull-access control exists to withhold from non-members: the ciphertext itself plus its traffic metadata (that the doc changes, when, and roughly how much). For the framework this is the relay-tier gate of the pull/read separation, so we want it closed upstream.
Reproduction
spikes/skeleton/, just run (needs a polymorph-iroh checkout; see its README). The scenario prints the observed branch explicitly; the assertion accepts either upstream behavior and reports which occurred, so a fixed upstream flips the output to v3 ciphertext NOT pushed to the revoked subscriber with no spike changes.
Pins: subduction rev 2401102882b97d6b13c559fbbcfc2257e3a46dd3, keyhive rev efe6ccf via [patch.crates-io].
Suggested fix shapes (either suffices)
- Re-check
authorize_fetch (or run filter_authorized_fetch) per subscriber at notification time; or
- tear down a peer's subscriptions for a sedimentree when its authorization is lost (the
subduction_keyhive bridge learns this when it ingests the revocation).
Draft upstream issue (for inkandswitch/subduction — do not file without explicit go)
Title: [bug]: subscription push skips StoragePolicy — revoked peers keep receiving new commits
Setup: subduction_core + subduction_keyhive at rev 2401102, two peers, SubductionKeyhive as the connection/storage policy, keyhive docs with SedimentreeId = keyhive DocumentId. Peer B syncs a tree with subscribe: true while holding Access::Read, then is revoked (keyhive revocation ingested on both sides; transitive_members() no longer contains B).
Observed: after revocation, new add_commits on peer A are still pushed to B over the existing subscription — B's sedimentree keeps growing. Explicit sync_with_peer calls from B are refused correctly (empty diff), so the gap is specific to the push path: the subscriber list is built at sync time and notifications do not re-consult StoragePolicy.
Expected: a peer that would fail authorize_fetch no longer receives pushed commits — either by re-checking the policy per subscriber at notification time, or by dropping the peer's subscriptions when authorization is lost.
Impact: bounded when content is E2E-encrypted (the revoked peer cannot decrypt new epochs), but the ciphertext and its traffic metadata keep flowing — the exact thing pull-level access control withholds from non-members, and the behavior a pull-only relay role would rely on.
Reproduction: two-peer scenario in a wasm32-wasip2 embedding, available at https://github.com/polymorph-components/polymorph-apps/tree/main/spikes/skeleton (the host prints which branch occurs; happy to minimize into a native test against ChannelTransport if useful).
Status
The finding
Observed in the walking-skeleton spike, phase 3b (spikes/skeleton/, commit 3177cbd; discussion on #8): with
subduction_keyhive'sSubductionKeyhivepolicy gating pull against the keyhive auth graph,sync_with_peerfrom a non-member (before add, and again after revocation) is refused with an empty diff (success=false, nothing sent — the no-information-leak posture);OBSERVED: v3 ciphertext still pushed to the revoked subscriberin the spike run).Mechanics: subduction reaches a peer two ways — request/response sync, which consults
StoragePolicy::authorize_fetch/filter_authorized_fetch, and subscription push, which walks the subscriber list built at sync time. Revocation updates the auth graph, but nothing removes the peer from the subscriber list and the push path does not re-check the policy.Impact is bounded by the crypto tier — BeeKEM revocation excluded the peer from new epochs, so everything pushed is undecryptable (
KeyNotFound, asserted in the spike) — but what leaks is precisely what pull-access control exists to withhold from non-members: the ciphertext itself plus its traffic metadata (that the doc changes, when, and roughly how much). For the framework this is the relay-tier gate of the pull/read separation, so we want it closed upstream.Reproduction
spikes/skeleton/,just run(needs a polymorph-iroh checkout; see its README). The scenario prints the observed branch explicitly; the assertion accepts either upstream behavior and reports which occurred, so a fixed upstream flips the output tov3 ciphertext NOT pushed to the revoked subscriberwith no spike changes.Pins: subduction rev
2401102882b97d6b13c559fbbcfc2257e3a46dd3, keyhive revefe6ccfvia[patch.crates-io].Suggested fix shapes (either suffices)
authorize_fetch(or runfilter_authorized_fetch) per subscriber at notification time; orsubduction_keyhivebridge learns this when it ingests the revocation).Draft upstream issue (for inkandswitch/subduction — do not file without explicit go)
Status