diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-01 21:51:25 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-01 21:51:25 +0200 |
| commit | 799d87999c8324564dce5159191532e008dd93d2 (patch) | |
| tree | 5ff1816f18625dfece9eb67fa06c7b25fdece4f8 /packages/meshbay-hub/tests/test_node_auth.py | |
| parent | 8a6294b0412a86f378c6e2e937c28de64a903c91 (diff) | |
| parent | 1e6db7d23c70b7bd7e1422f09911b3645f0fb2e2 (diff) | |
| download | meshbay-799d87999c8324564dce5159191532e008dd93d2.tar.gz | |
Merge branch 'fix/third-review-h1-h2-m1-m6'
Third security review (docs/third-review.md) plus its remediation.
Fixed and verified:
- H1 moderator could grant admin / hard-revoke → handler split by field
- H2 unauthenticated 2-report global blocklist → auth + distinct reporters
+ rate limit + refused when public groups are off
- M1 registration reCAPTCHA was inert → gate unconditional; the
desktop client's CSP allows the widget
- M2 QUIC chat/stream handlers lagged WebRTC → brought to parity; the QUIC
listener is now off by default ([node] quic_enabled)
- M3 link-preview SSRF gaps → rate limit + port allowlist
+ connect-address re-check + decompression-bomb guard
- M4 federated peer over-trust → source bound to the signer,
push capped, revocation prunes the peer's own entries, replay rejected
- M5 no CSP / security headers on the SPA → middleware; verified against
the live app with no violations
Withdrawn:
- M6 add_group_member accepting node tokens is deliberate (commit 0443cf8,
the CLI invite flow). The "fix" broke that flow on the deployed hub and
was reverted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pG75yGK3NthNfyjH74omG
Diffstat (limited to 'packages/meshbay-hub/tests/test_node_auth.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_node_auth.py | 23 |
1 files changed, 17 insertions, 6 deletions
diff --git a/packages/meshbay-hub/tests/test_node_auth.py b/packages/meshbay-hub/tests/test_node_auth.py index 72ce412..a104a72 100644 --- a/packages/meshbay-hub/tests/test_node_auth.py +++ b/packages/meshbay-hub/tests/test_node_auth.py @@ -148,24 +148,35 @@ async def test_node_scope_blocks_group_create(client): @pytest.mark.asyncio -async def test_node_scope_blocks_add_member(client): - sk_node, user_token = await _setup_node_user(client, "op1") +async def test_node_token_may_add_a_member_to_its_own_operators_group(client): + """The node calls this after a CLI `member invite` so the group shows up in + the invitee's SPA (commit 0443cf8). A node-scoped token is accepted here — + the `group.admin_id == caller` check is the guard — but only for a group the + node's operator owns.""" + sk_op, op_token = await _setup_node_user(client, "op1") r = await client.post("/v1/groups", json={ "name": "mygroup", "visibility": "private", "join_policy": "invite", - }, headers={"Authorization": f"Bearer {user_token}"}) - assert r.status_code == 201 + }, headers={"Authorization": f"Bearer {op_token}"}) gid = r.json()["group_id"] _, pk2 = _gen_ed25519() _, px2 = _gen_x25519() await _register(client, "member1", pk2, px2) - r = await _node_auth(client, "op1", sk_node) - node_token = r.json()["access_token"] + node_token = (await _node_auth(client, "op1", sk_op)).json()["access_token"] r = await client.post(f"/v1/groups/{gid}/members/member1", headers={"Authorization": f"Bearer {node_token}"}) + assert r.status_code == 201 + + # …but not to a group it does not own. + sk_other, other_token = await _setup_node_user(client, "op2") + r = await client.post("/v1/groups", json={"name": "theirs", "visibility": "private"}, + headers={"Authorization": f"Bearer {other_token}"}) + other_gid = r.json()["group_id"] + r = await client.post(f"/v1/groups/{other_gid}/members/member1", + headers={"Authorization": f"Bearer {node_token}"}) assert r.status_code == 403 |