fix(collab): hull-patch part 2 — owner-rotation lockout + keyless-add + import-tolerance (#265) - #266
Merged
Merged
Conversation
…the review (#265) The daemon-security bugs deferred from the pre-dogfood review (#188), each done in a careful pass. Dogfood is hub-scoped, so these are the hub-relevant correctness/security fixes. 1. Owner rotation -> self-lockout on an un-anchored (owned/hub) KB [FIXED + tested]. The rotation-successor -> member_roles mirror ran ONLY on the member self-service branch; the owner (Manage=Allow) left rebind_pairs empty, so after the documented rotate+reconnect the owner got role_of(new_fp)=None -> Deny on their own KB. Added owner_self_rebind_pairs: it extracts ONLY the authenticated principal's OWN self-Rebinds (author==principal, self-signed, fresh successor bound to its key) from the update and mirrors them, inheriting the caller's role. It can NOT reuse verify_member_self_service_update (the owner's rotation update also carries E2E content-key re-wraps, which that strict gate rejects), and it can NOT inject an arbitrary member (predecessor is always the caller). Refactored the branch to a match expression. Tests: owner_self_rebind_is_mirrored_into_the_roster_no_lockout (the fix) + owner_path_does_not_mirror_a_rebind_authored_by_another_principal (adversarial: a Rebind authored by another principal yields no pair for a non-author caller). 2. Keyless add on an E2E KB [FIXED]. Adding a member by FINGERPRINT to an encrypted KB cannot wrap the content key (no published wrap key to seal to), so they were admitted keyless with success UX. The add now emits a prominent warning steering to the join->approve path (which seals the key); the add still proceeds so the member can be re-keyed on a later approve. Advisory at the authoritative point (the network task holds content_keys); not unit-tested (network-loop, not a security gate). 3. Import silently became an unpersisted in-memory KB [FIXED]. import_org_dir_to_store `?`-aborted on the first bad node (partial persistent state) and the editor silently swapped to an in-memory copy that vanished on restart. Now per-node error-tolerant (records into report.errors, continues; only tracks a node once it persisted), and a catastrophic store failure surfaces a prominent message-log ERROR instead of a silent ephemeral swap. Verification: daemon lib 144 + mae-core 2349 + mae-kb 217 green; workspace clippy --all-targets clean (both workspaces). Deferred from #265 (follow-ups): the links:by_dst index (perf, needs a covering Cozo index rewrite), and the heavier tests (>=3-member mesh/hub convergence, recovery-on-anchored, leaked-recovery-supersede, offline-across-rotation) — the e2e-harness + membership-fixture items. Tracked on #265. Refs #265, #188. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 tasks
cuttlefisch
added a commit
that referenced
this pull request
Jul 3, 2026
…ne note templates (#267) A full step-by-step manual test plan for validating the v0.15 release across two REAL machines over the mTLS hub (mesh deferred to v0.16, #263), refactored from the v0.14 drafts into canonical command names + the current feature surface. - docs/v0.15-two-machine-test-plan.md — master plan: Part 0 working protocol (topology, coordination board, probe slugs, daemon-log discipline, reproducibility), Part 1 setup (GUI build, mae --collab-identity ↔ mae-daemon authorize, daemon.toml key+mTLS on 0.0.0.0:9473, mae setup-collab + init.scm), Part 2 the full H1–H13 matrix (connect/mTLS, buffer convergence, share→pending→approve→join, bidirectional KB CRDT, roles/policy, E2E + key-blind canary oracle, member-removal key-rotation with forward-secrecy + history-retention, identity rotation INCLUDING the owner self-lockout regression check for the #266 fix, recovery-key, offline-merge, concurrent same-node, restart survival, and security-negative). Appendix: verify surfaces, file locations, troubleshooting. - docs/v0.15-test-notes-alice.md / -bob.md — per-machine fill-in note templates (environment block, V-NN issues, H1–H13 scenario log rows from each role). Commands + daemon-log oracles cross-checked against the CLI and the automated scripts/collab-{mtls,membership,encrypted}-e2e.sh — all six e2e smoke runs (base + REMOVAL/ROTATE/RECOVER gates) pass locally with the freshly-built release binaries, and the daemon log strings the plan asserts (kb/join: pending, kb/approve_member: complete, kb/join: complete node_count=3, mTLS client authenticated, reason=role) match verbatim. The v0.14 drafts are left intact as case-study. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hull-patch part 2 — the three daemon-security bugs (#265)
The daemon-security bugs deferred from the pre-dogfood review (#188), each done in a careful pass with the owner-rotation one adversarially tested. Dogfood is hub-scoped, so these are the hub-relevant correctness/security fixes.
1. Owner rotation → self-lockout on an un-anchored (owned/hub) KB 🔴 (fixed + tested)
The rotation-successor →
member_rolesmirror ran only on the member self-service branch; the owner (Manage = Allow) leftrebind_pairsempty, so after the documented rotate→reconnect the owner gotrole_of(new_fp) = None→ Deny on their own KB.Added
owner_self_rebind_pairs: extracts only the authenticated principal's own self-Rebinds (author == principal, self-signed, fresh successor bound to its key) and mirrors them, inheriting the caller's role. It deliberately does not reuseverify_member_self_service_update(the owner's rotation update also carries E2E content-key re-wraps, which that strict gate rejects), and it cannot inject an arbitrary member (the predecessor is always the caller). The branch is refactored to a clean match expression.owner_self_rebind_is_mirrored_into_the_roster_no_lockout— the fix (successor inherits Owner).owner_path_does_not_mirror_a_rebind_authored_by_another_principal— adversarial (a Rebind authored by a different principal yields no pair for a non-author caller).2. Keyless add on an E2E KB 🟠 (fixed)
Adding a member by fingerprint to an encrypted KB can't wrap the content key (no published wrap key to seal to), so they were admitted keyless with success UX. The add now emits a prominent warning steering to
:kb-join→:kb-approve(which seals the key); the add still proceeds so they can be re-keyed on a later approve. Advisory sits at the authoritative point (the network task holdscontent_keys); it's UX defense-in-depth, not a gate, so no network-loop unit test.3. Import silently became an unpersisted in-memory KB 🟠 (fixed)
import_org_dir_to_store?-aborted on the first bad node (partial persistent state) and the editor silently swapped to an in-memory copy that vanished on restart. Now per-node error-tolerant (records intoreport.errors, continues; tracks a node only once it persisted), and a catastrophic store failure surfaces a prominent message-log ERROR instead of a silent ephemeral swap.Verification
daemon lib 144 + mae-core 2349 + mae-kb 217 green; workspace clippy
--all-targetsclean (both workspaces).Deferred (follow-ups on #265)
The
links:by_dstindex (perf — needs a covering Cozo-index rewrite oflinks_to, not security) and the heavier tests (≥3-member convergence, recovery-on-anchored, leaked-recovery-supersede, offline-across-rotation) — e2e-harness + membership-fixture work kept separate from the security fixes.