Skip to content

security: an unrooted vault gives the owner verbs to its embedded owner alone, and a self-hosted root binds that owner (H149) - #1476

Open
olety wants to merge 18 commits into
mainfrom
sec/agents-h149
Open

olety wants to merge 18 commits into
mainfrom
sec/agents-h149

Conversation

@olety

@olety olety commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

H149: on a vault with no authority root, any PERSON could take the owner verbs (medium; triage W3-T7 in the Codex security scan triage, triage-W3.md H149)

Finding

verify_owner_actor_binding_in_txn (memory/support.rs) asks the authority log whether an actor is the owner: an active human binding under an owner-capable key. A vault with no folded genesis skipped that question and passed every live PERSON. A default embedded vault is unrooted, so a host acting as any PERSON row, an agent's persona included (napi as_actor("human:<persona>")), could mint PERSON rows, retract another actor's claims and safe-delete as the owner. Rooted vaults were also unfinished: no production path bound the owner, so a rooted vault refused its own owner.

Checks (fail before)

In memory/tests/authority_revocation.rs:

  • an_unrooted_vault_gives_no_other_person_the_owner_verbs: a persona row named "human" and a second PERSON are refused the PERSON mint, the cross-actor retract and the safe-delete.
  • an_unrooted_vault_keeps_the_owner_verbs_for_its_embedded_owner: the positive control.
  • a_deleted_embedded_owner_holds_no_owner_verbs: after a user delete, OWNER_BINDING_REQUIRED; after a hard delete, refused.
  • a_self_hosted_root_binds_its_owner_and_no_one_else: after ensure_host_root_slip, the embedded owner passes and the persona is refused.
  • owner_verbs_require_active_owner_binding_when_rooted (on main already) still passes.

In claim/tests.rs, expression_preference_override_refuses_a_human_who_is_not_the_owner: a PERSON named human neither retracts an agent's preference nor replaces it with an explicit one. In critic/review/tests.rs, document_and_claim_reviews_persist_and_learn_dismissals now also has a delegate PERSON refused the owner verdict.

The owner-write verifier and the owner-proof doors:

  • only_the_owner_or_the_scope_actor_takes_a_standing_grant_away (consent_graduation/tests.rs): a native owner proof for a live PERSON of this vault who is not its owner neither demotes the owner's standing grant nor rules on a proposal.
  • a_person_who_is_not_the_owner_cannot_write_as_the_owner (channel_identity_selection/tests.rs): that proof amends no owner-locked selection law, and nothing is stored.
  • an_unclear_verdict_changes_no_reliability_and_files_one_note (skill_attribution/tests/sweep.rs): a Human who is not the owner moves no learning-setting floor (put_setting_row, through verify_owner_write_actor_in_txn).

Fail-before: main 14fcd7d's code in the nine non-test files this PR changes (authority/host_actor.rs removed), with this branch's tests. 8 of the 10 checks fail at their assertions: the four new authority_revocation checks, expression_preference_override_refuses_a_human_who_is_not_the_owner, document_and_claim_reviews_persist_and_learn_dismissals, only_the_owner_or_the_scope_actor_takes_a_standing_grant_away, a_person_who_is_not_the_owner_cannot_write_as_the_owner and an_unclear_verdict_changes_no_reliability_and_files_one_note. The other two pass on main as they should: the positive control an_unrooted_vault_keeps_the_owner_verbs_for_its_embedded_owner and owner_verbs_require_active_owner_binding_when_rooted.

Fix

  • Unrooted: the embedded owner alone. On an unrooted vault only embedded_owner_actor_id(), live, passes. Every other PERSON gets OWNER_BINDING_REQUIRED, with the advice to root the vault under its host key. Multi-root stays INVALID_STATE.
  • Every owner predicate asks the same check. The expression-preference retract and explicit replacement (explicit_claim_override_in_txn) and the critic's owner verdict kept their own copy of the predicate, with the same unrooted pass-through. They now call verify_owner_actor_binding_in_txn.
  • The owner-write verifier asks it too. verify_owner_write_actor_in_txn (authority/write_authorization.rs) checked the Human class only, and so passed any PERSON on an unrooted vault. It now also calls verify_owner_actor_binding_in_txn. Its callers: learning settings, preference-principal binds, the optimizer brief and job for a principal, inbox resolution as an actor, project depth, the subject model and its restore review, cloud account recovery, and machine-claim approval.
  • Owner-proof doors ask whose vault it is. A native AuthenticatedOwner shows a live PERSON, not that the PERSON owns this vault. record_proposal_outcome_for_ramp and demote_scope_to_propose (consent_graduation/doors.rs), and the selection law's Owner writer (channel_identity_selection/selection_rules.rs), revalidated for liveness only. They now take revalidate_as_vault_owner_in_txn, whose body is unchanged.
  • A self-hosted root binds its owner. When ensure_host_root_slip writes the genesis, and the vault's posture is SelfHostLocal, the same write transaction binds the embedded owner as human, epoch 1, under the host root key (authority/host_actor.rs). A hosted root is the operator's key, never the owner's, and a relay refuses rooting. Neither binds anyone; there the owner binds from a device of their own. A hard-deleted owner is not brought back, so that vault stays rooted with no owner bound.
  • No migration: vaults rooted before this change keep no owner binding (pre-GA).
  • oneiron serve in dev mode with no auth secret stays loopback-only unless --allow-unauthenticated (crates/oneiron-server/src/commands.rs:618).

Test fixtures

Tests that named an arbitrary PERSON as the owner of an unrooted vault relied on the pass-through, about 300 of them across the workspace. On main each of those PERSONs held the owner verbs, so each fixture now names the owner explicitly:

  • The vault's embedded owner, where the fixture's vault stays unrooted (put_owner, session_owner_fixture and ensure_embedded_owner_actor in the memory, entity_doc, off_record, workspace_roster, recovery and napi tests).
  • Or test_util::bind_test_owner, which roots the vault under the test host and binds the test's own owner, where the test needs a specific id (booking publication, connector keys, script runtime, share, tagging, outbound consent and others).
  • A host key holds one live binding, and a self-hosted root now binds the embedded owner. So bind_host_actor_for_test and the server's binding_op rebind the key one epoch on when a fixture names another actor, and are no-ops when it already holds that binding.
  • The owner-write verifier and the F24 doors: the graduation, selection-law, native-mail and merge-split oracle owners are the embedded owner. unrooted_anchor_uses_canonical_human_owner_semantics and unrooted_substrate_uses_canonical_human_owner_semantics tested the old unrooted rule; they now check that a Human PERSON who is not the owner is refused, then write as the embedded owner.
  • Three preference-isolation tests keep a second principal past the owner check. Its receipt is bound as by an earlier owner (bind_amendment_preference_principal_in_txn, the binding row's in-transaction door), and its brief is read through optimize_brief_bound_at. The public doors refuse it.
  • In the mixed-room fixture Bob is not the vault's owner, and the room's rows carry no record scope a read grant could name, so the owner reads his message, before and after the tombstone.
  • Authority tests that hard-coded the log's sequence numbers or parents after a root (slip_tests.rs, tests/machine_write.rs) now read them from the fold, since the root writes one more entry. owner_approval_and_machine_supersession_follow_signed_history uses the owner the root bound in place of a hand-signed bind.

Overlap with #1422

#1422 (not merged) edits three files this PR touches, on other lines:

Production binds under the host key

A host key holds one live binding, so anything else bound under the root key after genesis would rebind it away from the owner. No production path does that. The only production BindActor writers are the owner bind here (authority/host_actor.rs) and machine enrollment (authority/machine_enroll.rs:107-117), which binds the machine's own fresh key (engine machines included). Agent provisioning (ensure_agent_principal) writes a PERSON row and no binding. Token bootstrap, pairing and serve-time root slips mint slips under the root key and bind no one. A HostedNode profile is a machine-role admission rule (authority/machine_profile.rs:199), not a bind. Re-root runs ensure_host_root_slip_in_txn on a non-empty log, so it writes no genesis and no owner bind. Only the test fixtures rebind the host key.

Follow-ups

  • The embedded owner's own-device read lane keeps owner reads after the owner's PERSON row is soft-deleted, or after a rooted vault's owner binding is revoked. It also skips the fold's multi-root check: actor_is_vault_owner_in_txn (memory/credential_key.rs:128) and owner_live_in (claim/scoped_read/admission.rs:346) both pass the embedded id outright. This is unchanged from main and is a read path, not an owner verb (Sol R1 F2, Astra R1 F3; logged by the Engine as a medium follow-up).
  • Carrying the owner binding through a re-root.
  • One owner set: policy_model::is_live_vault_owner_in_txn names the embedded owner of a personal vault without asking the authority log, so after an actor rebind on a rooted vault it still names the former owner. That set backs revalidate_as_vault_owner_in_txn and the F26 weave-correction owner exemption (claim/scoped_read/weave_correction.rs:80-97). Unify it with the authority binding (Astra R2 F3).
  • Native owner proofs at the other owner-only doors: about 40 doors take an AuthenticatedOwner and revalidate it for liveness only, so a live PERSON's native proof passes them (consent approve_once and standing grants, owner reasons, widen acceptance, manifest and pack policy, imports, checkpoints, cleanup, project mint and goals, and others; Sol R3 F2, Astra R3 F2). Unchanged from main. This PR switches only the F24 doors; the tier flip (sec2/forged-authority-tier, stacked on security: owner proofs act only in the vault and tier that authenticated them (F18, H397, H224) #1458) closes the rest, since every one of them revalidates through revalidate_in_txn.
  • The re-routed owner checks refuse a conflicted root but report it as an ordinary authority denial (FORBIDDEN, or BAD_REQUEST from the preference retract's own fold check), not INVALID_STATE (Sol R2 F3, Astra R2 F4; low). The same holds for a fold past its budget (security: bound authority-fold work, hold rows no owner signed, floor-check cosigners (H72, H71 residual) #1449, FoldBudgetExceeded) and for a storage error.
  • A rooting door for embedded SDK hosts (napi), so an embedded host can root and bind its owner, and naming that step in the napi as_actor docs.

Pushbacks

  • Sol R3 F1 and Astra R3 F1, the remaining part: in legacy_evidence_and_other_principals_do_not_cross_the_miner_threshold, B's inbox refusal still stops at the owner check, not the decider check. A vault has one owner, so no second owner exists to reach the decider check.

Reviews

  • Round 1, Sol xhigh and Astra max on 8580611. Both found the same set. Two copies of the owner predicate kept the unrooted pass-through: the expression-preference retract and explicit replace (Sol F3, Astra F1) and the critic owner verdict (Sol F4, Astra F2). The read lane is deferred (Sol F2, Astra F3; see Follow-ups). A fixture made the speaker the owner (Sol F1, P3). Everything except the read lane is fixed in 8697fb9.
  • Round 2, Sol xhigh and Astra max on 684f105. The owner-write verifier kept its own Human-only check (Sol F1, Astra F1, P1), and the merged F24 doors took a native proof as the owner's (Sol F2, Astra F2, P1); both are fixed in 696a506. Three fixtures lost their non-owner control (Sol F4, Astra F5, Astra F6) and are fixed there too. The policy_model owner set (Astra F3) and the INVALID_STATE classification (Sol F3, Astra F4) are follow-ups.
  • Round 3, Sol xhigh and Astra max on 5d4f7bd: no P1 or P2. Three preference-isolation tests stopped at the new owner check (Sol F1, Astra F1, P3); they now also check isolation past it, in 0fb15de. The inventory of other owner-proof doors (Sol F2, Astra F2) is a follow-up.

Gate

GREEN on 8db67b5 (gce-3, 24G, 10 threads, 8 test threads; fleet job 1016-011215-66a2):

  • precheck (the modules of the r4 failures and of main's new tests): 461 of 461;
  • fmt, generated, clippy (three scopes, with strict rustdoc) and check-shipped-bin: OK;
  • test: nextest 9306 of 9306 (20 skipped), plus 13 of 13; doctests 17 passed;
  • sync-lane: 7409 of 7409 (19 skipped);
  • featureless: 6281 of 6281 (1 skipped).

The gate before it, on 0bf2034, went red at clippy on an unused test binding (fixed in e818ef4). Two checks that main's #1458 added also failed, because their owner helper used an unbound PERSON (connector_key::tests::foreign_owner_proof; 8db67b5 binds it).

olety added 5 commits October 14, 2026 22:45
…s (H149)

Fails on origin/main 95822ce: a persona PERSON mints a PERSON on an
unrooted vault (put_structural), as the embedded owner does.
…er alone, and a self-hosted root binds that owner (H149)

On a vault with no authority root, verify_owner_actor_binding_in_txn passed
every live PERSON, so a persona or agent row named "human" could mint,
retract and delete as the owner. Now only the vault's own live embedded owner
passes there; every other PERSON gets OWNER_BINDING_REQUIRED with the rooting
advice. Multi-root stays INVALID_STATE.

A self-hosted genesis (ensure_host_root_slip on an empty log) binds the
embedded owner as human, epoch 1, in the same transaction. Hosted and relay
roots bind no one; a hard-deleted owner stays unbound.

Test fixtures that used an arbitrary PERSON as the owner of an unrooted vault
now use the embedded owner, or root the vault and bind theirs.
…dict take the shared owner check (H149 review round 1)

Both kept their own copy of the owner predicate, which passed every human
PERSON on a vault with no authority root (Astra R1 F1, F2). They now ask
verify_owner_actor_binding_in_txn, so on an unrooted vault only the embedded
owner retracts or explicitly replaces another actor's expression preference,
or records an owner verdict.

The promoted-session test keeps its own speaker, distinct from the owner who
promotes, and reads the promoted turn as the owner (Sol R1 F1).
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

This review includes 92 billable files and costs up to $23.00.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 23 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 76 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: oneiron-dev/oneiron/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: ae198b4a-5fa2-4589-8464-e976640ab481
📥 Commits

Reviewing files that changed from the base of the PR and between e3b3e1f and 8db67b5.

📒 Files selected for processing: 92
  • crates/oneiron-napi/src/facade/tests.rs
  • crates/oneiron-server/src/api/tests/conversation_reactions.rs
  • crates/oneiron-server/src/api/tests/support_mcp_credentials.rs
  • crates/oneiron-server/src/test_credentials.rs
  • crates/oneiron/src/authority/host_actor.rs
  • crates/oneiron/src/authority/mod.rs
  • crates/oneiron/src/authority/slip_tests.rs
  • crates/oneiron/src/authority/slip_vault.rs
  • crates/oneiron/src/authority/tests/machine_write.rs
  • crates/oneiron/src/authority/write_authorization.rs
  • crates/oneiron/src/booking/anti_abuse/scope_tests.rs
  • crates/oneiron/src/booking/emergency_reschedule/tests/effects.rs
  • crates/oneiron/src/booking/emergency_reschedule/tests/host_binding.rs
  • crates/oneiron/src/booking/emergency_reschedule/tests/instruction.rs
  • crates/oneiron/src/booking/emergency_reschedule/tests/mod.rs
  • crates/oneiron/src/booking/emergency_reschedule/tests/owner_revocation.rs
  • crates/oneiron/src/booking/publication/tests.rs
  • crates/oneiron/src/channel_identity_provider/native_mail/tests.rs
  • crates/oneiron/src/channel_identity_selection/selection_rules.rs
  • crates/oneiron/src/channel_identity_selection/tests.rs
  • crates/oneiron/src/claim/expression_preference.rs
  • crates/oneiron/src/claim/tests.rs
  • crates/oneiron/src/connector_key/qualification/tests.rs
  • crates/oneiron/src/connector_key/slate.rs
  • crates/oneiron/src/connector_key/tests.rs
  • crates/oneiron/src/consent_graduation/doors.rs
  • crates/oneiron/src/consent_graduation/tests.rs
  • crates/oneiron/src/critic/review/outcomes.rs
  • crates/oneiron/src/critic/review/tests.rs
  • crates/oneiron/src/edit_distance/graduation/tests.rs
  • crates/oneiron/src/edit_distance/miner/feedback.rs
  • crates/oneiron/src/edit_distance/miner/tests.rs
  • crates/oneiron/src/edit_distance/miner/tests/preference_learning.rs
  • crates/oneiron/src/entity_doc/tests.rs
  • crates/oneiron/src/gate/tests/charter_ceiling.rs
  • crates/oneiron/src/gate/tests/external_effect_grants.rs
  • crates/oneiron/src/lens/tests/selection_handles.rs
  • crates/oneiron/src/llm/decision/questions/standing_tests.rs
  • crates/oneiron/src/m8_forward_oracle/shared.rs
  • crates/oneiron/src/memory/authorship.rs
  • crates/oneiron/src/memory/chat/tests.rs
  • crates/oneiron/src/memory/key_value/tests.rs
  • crates/oneiron/src/memory/support.rs
  • crates/oneiron/src/memory/tests/authority_revocation.rs
  • crates/oneiron/src/memory/tests/commit_claims.rs
  • crates/oneiron/src/memory/tests/delete_tombstone.rs
  • crates/oneiron/src/memory/tests/mod.rs
  • crates/oneiron/src/memory/tests/session_recall.rs
  • crates/oneiron/src/memory/tests/session_witness.rs
  • crates/oneiron/src/memory/tests/takes_notes.rs
  • crates/oneiron/src/memory/tests/witness_policy.rs
  • crates/oneiron/src/memory/tests/witness_turns.rs
  • crates/oneiron/src/memory/tests_regressions/authority.rs
  • crates/oneiron/src/memory/tests_regressions/consolidation_outbound.rs
  • crates/oneiron/src/memory/tests_regressions/mod.rs
  • crates/oneiron/src/memory/tests_regressions/outbound_actor_scope.rs
  • crates/oneiron/src/memory/tests_regressions/recall/bounds_execution.rs
  • crates/oneiron/src/memory/tests_regressions/recall/call_mode.rs
  • crates/oneiron/src/memory/tests_regressions/recall/clock_consistency.rs
  • crates/oneiron/src/memory/tests_regressions/recall/pack_scope.rs
  • crates/oneiron/src/memory/tests_regressions/recall/read_grants.rs
  • crates/oneiron/src/memory/tests_regressions/recall/temporal.rs
  • crates/oneiron/src/memory/tests_regressions/recall/turn_fold.rs
  • crates/oneiron/src/memory/tests_regressions/recall_revision.rs
  • crates/oneiron/src/memory/witness/stream/tests.rs
  • crates/oneiron/src/off_record/tests.rs
  • crates/oneiron/src/outbound_consent/tests.rs
  • crates/oneiron/src/reaction/tests/recall.rs
  • crates/oneiron/src/recovery/canonical_tests.rs
  • crates/oneiron/src/recovery/checkpoint/decisions/tests/consent.rs
  • crates/oneiron/src/saved_query/tests.rs
  • crates/oneiron/src/self_heal/production_tests.rs
  • crates/oneiron/src/share/tests.rs
  • crates/oneiron/src/skill_attribution/tests/sweep.rs
  • crates/oneiron/src/skill_hub/pack_catalog/script_runtime/tests.rs
  • crates/oneiron/src/skill_optimize/brief.rs
  • crates/oneiron/src/skill_optimize/mod.rs
  • crates/oneiron/src/skill_optimize/tests.rs
  • crates/oneiron/src/subject_model/tests/archive.rs
  • crates/oneiron/src/subject_model/tests/authorization.rs
  • crates/oneiron/src/subject_model/tests/substrate_authorization.rs
  • crates/oneiron/src/tagging/save_tests.rs
  • crates/oneiron/src/tagging/tests.rs
  • crates/oneiron/src/test_util/mod.rs
  • crates/oneiron/src/vault/entity_revision/tests.rs
  • crates/oneiron/src/vault_cleanup/retention_tests.rs
  • crates/oneiron/src/workspace_roster/project/goal/interview/tests.rs
  • crates/oneiron/src/workspace_roster/project/goal/tests.rs
  • crates/oneiron/src/workspace_roster/project/tests.rs
  • crates/oneiron/src/workspace_roster/rooms/tests.rs
  • crates/oneiron/tests/it/merge_split_oracle.rs
  • crates/oneiron/tests/it_project_conversion.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

olety added 9 commits October 15, 2026 18:26
… turned up (H149)

Each of these tests acted as an arbitrary PERSON that held the owner verbs
or the owner's reads on a vault with no authority root. They now act as the
vault's embedded owner, or root the vault and bind the test's owner before
they measure or read. The rooted-authority booking test opens its vault
unrooted, since it writes its own genesis.
…t is (H149 review round 2)

verify_owner_write_actor_in_txn checked the Human class and stopped there;
it now asks verify_owner_actor_binding_in_txn, as every owner verb does, so
on an unrooted vault only the embedded owner writes learning settings, binds
preference principals, reads the optimizer brief for a principal, resolves
the inbox as itself, and edits project depth or the subject model.

record_proposal_outcome_for_ramp, demote_scope_to_propose and the selection
law's Owner writer revalidated a native owner proof for liveness only. They
now take revalidate_as_vault_owner_in_txn, so a live person of this vault who
is not its owner rules nothing and amends no owner-locked law.

Checks that fail on main: a non-owner person's proof is refused by both
graduation doors and by the selection Owner writer, and a non-owner Human
moves no learning-setting floor. Fixtures that named an arbitrary person as
owner of these doors now use the embedded owner; Bob reads his own room
message under a read grant, and the decisions census signs principal autonomy
as the embedded owner.
…heck (H149 review round 3)

Round 3 (Sol F1, Astra F1): with only the owner binding a preference
principal, the second principal in three isolation tests was refused at the
owner check and never reached the filters the tests are about.

B's receipt in the miner threshold test is now bound as by an earlier owner,
through the binding row's in-transaction door, so it is real evidence the
miner keeps apart from A's; B binding more evidence through the public door
is refused. The two brief tests keep the public refusal and also read B's
brief past the owner check (optimize_brief_bound_at), which carries none of
A's corrections or rejected drafts.
…r (H149 r4)

The owner-write verifier now refuses a Human PERSON who is not the owner,
so the two unrooted subject-model tests check that refusal and then write as
the embedded owner, and the archive test's source facts are authored by the
source's embedded owner.

In the mixed-room fixture Bob is not the vault's owner, and the room's rows
carry no record scope a read grant could name, so a read permit gives him no
read. The owner resolves and reads Bob's message instead, before and after
the tombstone and after Bob's erasure.
…H149, main's #1477 test)

Its imported evidence is private, which only the vault's owner reads; the test named a fresh PERSON, the owner only through the unrooted pass-through.
olety added a commit that referenced this pull request Oct 15, 2026
…8 tier flip, #1476 r3 tables)

A denial names no addressee, and self-heal reads each denied receipt as
the owner's signal, so a non-owner's refusal must not land. The weave
digest schedule is owner-authorized even for a person's own reader. Both
move from the person tier back to the owner tier and join the owner-door
table check.
olety added 4 commits October 15, 2026 23:57
# Conflicts:
#	crates/oneiron/src/channel_identity_selection/selection_rules.rs
#	crates/oneiron/src/channel_identity_selection/tests.rs
#	crates/oneiron/src/memory/tests/commit_claims.rs
@olety
olety marked this pull request as ready for review October 15, 2026 18:26
@greptile-apps

greptile-apps Bot commented Oct 15, 2026

Copy link
Copy Markdown

Automatic reviews are disabled for this repository.

@cursor

cursor Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

PR Summary

High Risk
Changes authentication and authorization for owner-only writes across vault lifecycle (unrooted vs rooted), with broad test fixture updates; incorrect binding logic could lock out legitimate owners or leave privilege escalation paths.

Overview
H149 tightens who may exercise vault owner verbs: on an unrooted vault, only the live embedded owner passes verify_owner_actor_binding_in_txn; any other PERSON named human gets OWNER_BINDING_REQUIRED instead of inheriting owner powers from store truth alone.

Self-hosted genesis now binds that embedded owner as human under the host root in the same transaction as ensure_host_root_slip (bind_rooted_owner_in_txn). Test/server bind helpers use shared binding_op and rebind when a fixture names a different actor than the one the root already bound.

Owner enforcement is centralized: verify_owner_write_actor_in_txn, expression-preference override/retract, critic owner verdicts, and several owner-proof doors call the same binding check or revalidate_as_vault_owner_in_txn instead of treating any live PERSON proof as the vault owner.

The bulk of the diff updates tests and fixtures (put_owner, bind_test_owner, embedded owner) so they explicitly root/bind the owner rather than relying on the old unrooted pass-through.

Reviewed by Cursor Bugbot for commit 8db67b5. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8db67b5. Configure here.

authority_key: issuer.public_key(),
actor_ref: owner,
actor_class: ACTOR_CLASS_HUMAN.into(),
epoch: 1,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soft-deleted owner rebound at root

Medium Severity

bind_rooted_owner_in_txn skips only a hard-deleted embedded owner, then writes an active human bind for a user-deleted shell. After a self-hosted root, owner verbs consult that bind and not liveness, so a UserDelete of the owner that already strips the verbs on an unrooted vault restores them as soon as the vault is rooted.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8db67b5. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant