Skip to content

fix(collab): hull-patch part 2 — owner-rotation lockout + keyless-add + import-tolerance (#265) - #266

Merged
cuttlefisch merged 1 commit into
mainfrom
fix/hull-patch-part2-255security
Jul 3, 2026
Merged

fix(collab): hull-patch part 2 — owner-rotation lockout + keyless-add + import-tolerance (#265)#266
cuttlefisch merged 1 commit into
mainfrom
fix/hull-patch-part2-255security

Conversation

@cuttlefisch

Copy link
Copy Markdown
Owner

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_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) = NoneDeny 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 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 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 holds content_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 into report.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-targets clean (both workspaces).

Deferred (follow-ups on #265)

The links:by_dst index (perf — needs a covering Cozo-index rewrite of links_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.

…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>
@cuttlefisch cuttlefisch added the release:none Skip version bump on merge label Jul 3, 2026
@cuttlefisch
cuttlefisch merged commit fdcea97 into main Jul 3, 2026
14 checks passed
@cuttlefisch
cuttlefisch deleted the fix/hull-patch-part2-255security branch July 3, 2026 09:34
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:none Skip version bump on merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant