From 6b38704d459dec1271c7ca883de3eb14190218f7 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Tue, 1 Sep 2026 17:49:19 +0200 Subject: fix(hub): require user scope to add group members MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every mutating group endpoint depends on require_user_scope except POST /v1/groups/{group_id}/members/{username}, which depended on get_current_user — so a node-scoped daemon token (or a stolen one) whose subject owns the group could add any existing user to it, contradicting NS7 ("operator manages groups from the browser only"). test_node_auth.py::test_node_scope_blocks_add_member already existed and was red on main; it passes now. Third security review, finding M6. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_011pG75yGK3NthNfyjH74omG --- packages/meshbay-hub/src/meshbay_hub/api/groups.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/api/groups.py') diff --git a/packages/meshbay-hub/src/meshbay_hub/api/groups.py b/packages/meshbay-hub/src/meshbay_hub/api/groups.py index fdb0444..15c7a5d 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/groups.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/groups.py @@ -586,9 +586,14 @@ async def update_group( async def add_group_member( group_id: str, username: str, - current_user: User = Depends(get_current_user), + current_user: User = Depends(require_user_scope), db: AsyncSession = Depends(get_db), ): + # `require_user_scope`, like every other mutating group endpoint: a + # node-scoped daemon token must not manage membership (NS7 — the operator + # manages groups from the browser). This was the one membership endpoint + # still on `get_current_user`, so a node token could add members to its + # operator's own groups. group = await db.get(Group, group_id) if not group: raise HTTPException(status_code=404, detail="Group not found") -- cgit v1.2.3 From 1e6db7d23c70b7bd7e1422f09911b3645f0fb2e2 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Tue, 1 Sep 2026 20:50:45 +0200 Subject: revert(hub): M6 — add_group_member must keep accepting node tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M6 in the third review was a misread. `add_group_member` accepting a node-scoped token is deliberate (commit 0443cf8): the node calls POST /v1/groups/{id}/members/{username} after a CLI `member invite` so the group shows up in the invitee's SPA, authenticating with a node-scoped token. `group.admin_id == caller` is the real guard. An older test (`test_node_scope_blocks_add_member`) asserted the opposite and had been left red on main; the M6 "fix" (commit 6b38704) satisfied that test by switching the dependency to `require_user_scope` — which made `ops.create_invite`'s hub-membership call 403. That exception is swallowed with a log.warning, so an invited user silently never lands in group_members and the group is invisible to them. Reported from live testing (CLI `member invite grenet`, grenet saw nothing). Dependency back to `get_current_user`. The stale test now asserts the intended behaviour: a node token may add a member to its own operator's group (201) but not to a group it does not own (403). Third-review M6 marked WITHDRAWN. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_011pG75yGK3NthNfyjH74omG --- docs/third-review.md | 63 +++++++++++++--------- packages/meshbay-hub/src/meshbay_hub/api/groups.py | 14 ++--- packages/meshbay-hub/tests/test_node_auth.py | 23 +++++--- 3 files changed, 62 insertions(+), 38 deletions(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/api/groups.py') diff --git a/docs/third-review.md b/docs/third-review.md index 6839ef7..531f73e 100644 --- a/docs/third-review.md +++ b/docs/third-review.md @@ -75,7 +75,7 @@ and a few of the second-review fixes did not reach every path. running SPA. None of this breaks the architecture. The cryptographic core and the trust model -are unchanged and still sound. Every finding in this review (H1, H2, M1–M6) was +are unchanged and still sound. H1, H2 and M1–M5 were fixed on 2026-09-01; what is left is the L-list — opportunistic hardening, not a hole — plus verifying the SPA CSP (M5) against the live app. @@ -502,24 +502,32 @@ hashes to the `