Skip to content

fix(invite): refuse the host owner's own account joining as a guest - #1986

Open
panghy wants to merge 3 commits into
chore/remove-invite-redeemfrom
fix/invite-owner-self-join
Open

panghy wants to merge 3 commits into
chore/remove-invite-redeemfrom
fix/invite-owner-self-join

Conversation

@panghy

@panghy panghy commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Stack order — 19/22 of the intentd multiplayer stack. Base: #1969 (chore/remove-invite-redeem) · Next: #1988 (feat/invite-reusable-links).
Review only this PR's own commits (chore/remove-invite-redeem..fix/invite-owner-self-join); everything below is reviewed in its own PR.
Merge intent: the stack lands bottom-up in chain order through the merge queue, one PR at a time — this PR merges only after #1969 has landed. The whole intentd stack merges before the cloudlands-fe multiplayer stack (protocol rule: daemon side first); the cloudlands-fe stack must not be merged, auto-merge-armed, or queued until the intentd stack is merged.

Root cause

A guest whose GitHub account is the same account as the host owner's joined
via invite → gist proof. complete_invite_join resolved the proven
github_user_id to the existing primary principal row (is_primary = 1) and
minted a per-principal credential for it. The guest window's /ws connection
then authenticated as the primary: gated_collaborator_caller narrows only
non-administrator Wire callers to their memberships, and the primary owns
every workspace it created — so workspace.list returned every workspace on
the host with myRole: "owner" and the owner-only actions were offered.

Fix

  • InviteErrorKind::OwnerSelfJoin — wire error.data.code owner-self-join
    (-32602), message "the host owner cannot join its own host as a guest".
  • invite.prove / invite.accept refuse as soon as the identity is known
    (at prove: right after the gist proof resolves the account; at accept: when
    the presented credential's principal is the primary). No credential is minted,
    the invite stays open.
  • Store: join_workspace_by_invite guards the same invariant inside the
    transaction (InviteJoinOutcome::OwnerSelfJoin), so the is_primary row can
    never receive a principal_credential row through a join.
  • Docs: WorkspaceApi::invite_prove / invite_accept trait docs and the
    protocol.rs catalog module doc name the new kind.

Tests (commit 86a51bf1)

  • intent-store tests.rs: join_workspace_by_invite with the primary's
    identity answers OwnerSelfJoin, principal count unchanged, no credential row.
  • intent-services invite_ops/tests.rs: prove and accept with the owner's
    github_user_id answer owner-self-join; no principal_credential row.
  • intentd e2e_wss_invite_join.rs (real WSS): the owner's own gist proof on
    a pinned invite is -32602 / owner-self-join with no result; the invite
    remains open and the pinned guest joins it afterwards.
  • Existing capability::tests::visibility_by_role already asserts a
    collaborator Wire caller sees only its member workspaces on workspace.list.

Gates

cargo fmt --check, cargo clippy --workspace --all-targets -D warnings,
scoped nextest (117 passed: store/services/transport invite + capability),
e2e_wss_invite_join (8 passed), source lints (92 passed).

Protocol docs

docs/protocol/ in the monorepo does not enumerate InviteErrorKind codes as a
table; the catalog module doc in protocol.rs is updated here. If the monorepo
docs/protocol/methods invite section should list owner-self-join, that is a
follow-up monorepo docs PR.

@augmentcode augmentcode 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.

Deep Code Review Agent🐛

Review completed with 0 suggestions.

Reviewed commit: a85aabc

@augmentcode

augmentcode Bot commented Sep 19, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: This PR prevents a host owner from joining their own host through a guest invite.

Changes:

  • Adds the OwnerSelfJoin invite error and the owner-self-join wire code.
  • Maps the new error to JSON-RPC invalid-params (-32602).
  • Rejects primary-principal credentials during invite.accept.
  • Rejects a proven primary GitHub account during invite.prove.
  • Adds a transaction-level store guard before credentials, memberships, or invite redemption are written.
  • Propagates the new store outcome through the service layer.
  • Updates API and protocol catalog documentation for the new failure mode.
  • Adds store, service, and real-WSS coverage for the owner self-join path.
Technical notes: The invite remains open after refusal, and the guard is intended to ensure the primary principal never receives a guest credential.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode 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.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread crates/intent-core/src/error.rs
Comment thread crates/intentd/tests/e2e_wss_invite_join.rs Outdated
@panghy
panghy force-pushed the fix/invite-owner-self-join branch from a85aabc to 8445adf Compare September 19, 2026 14:35
@panghy
panghy force-pushed the fix/invite-owner-self-join branch 2 times, most recently from 819e109 to 994c8ba Compare September 19, 2026 19:01
panghy added a commit to intent-hq/cloudlands-fe that referenced this pull request Sep 20, 2026
…te flow

intentd refuses a join whose proven or presented identity is the host
owner's own account with error.data.code owner-self-join (intent-hq/intentd#1986),
so the guest never receives a credential for the primary principal. Add the
code to the closed InviteErrorCode set and give it its own failure sentence
in every locale: the owner is told to open the host from its paired daemons
instead of the generic "refused the invite" text. On invite.accept the code
propagates like workspace-full (no proof fallback).
panghy added a commit to intent-hq/cloudlands-fe that referenced this pull request Sep 20, 2026
…te flow

intentd refuses a join whose proven or presented identity is the host
owner's own account with error.data.code owner-self-join (intent-hq/intentd#1986),
so the guest never receives a credential for the primary principal. Add the
code to the closed InviteErrorCode set and give it its own failure sentence
in every locale: the owner is told to open the host from its paired daemons
instead of the generic "refused the invite" text. On invite.accept the code
propagates like workspace-full (no proof fallback).
panghy added a commit that referenced this pull request Sep 20, 2026
… member

`v["result"].is_null()` also passes for an envelope that wrongly carries
`"result": null`; the e2e now asserts the member is absent, which is what
the JSON-RPC error shape requires.

Addresses the Deep Code Review thread on #1986.
@panghy
panghy force-pushed the fix/invite-owner-self-join branch from 994c8ba to 4c7548c Compare September 20, 2026 09:06
@panghy
panghy added this pull request to stack #2028 September 20, 2026 12:50
panghy added a commit that referenced this pull request Sep 20, 2026
… member

`v["result"].is_null()` also passes for an envelope that wrongly carries
`"result": null`; the e2e now asserts the member is absent, which is what
the JSON-RPC error shape requires.

Addresses the Deep Code Review thread on #1986.
@panghy
panghy force-pushed the fix/invite-owner-self-join branch from 4c7548c to 9f90e54 Compare September 20, 2026 14:10
@panghy
panghy added this pull request to the merge queue Sep 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue because a pull request earlier in the stack was removed Sep 20, 2026
A join whose proven GitHub account is the primary principal's own
(same account on host and guest) is refused with the new
`InviteErrorKind::OwnerSelfJoin` (`error.data.code` `owner-self-join`,
-32602) at `invite.prove` and `invite.accept`, before any credential is
minted. The store's join transaction guards the same invariant
(`InviteJoinOutcome::OwnerSelfJoin`) so the `is_primary` row never
receives a per-principal credential; the invite stays open.

Motivation: a guest whose account matched the host owner's joined as
the primary principal and its window listed every workspace on the
host with owner actions.
Store, services and WSS e2e regressions: a prove/accept with the
primary principal's github_user_id answers `owner-self-join`, creates
no principal or principal_credential row, and leaves the invite open
for the pinned guest.
… member

`v["result"].is_null()` also passes for an envelope that wrongly carries
`"result": null`; the e2e now asserts the member is absent, which is what
the JSON-RPC error shape requires.

Addresses the Deep Code Review thread on #1986.
@panghy
panghy force-pushed the fix/invite-owner-self-join branch from 9f90e54 to f5e5b2d Compare September 20, 2026 15:29
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