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_admin.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_admin.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_admin.py | 39 |
1 files changed, 38 insertions, 1 deletions
diff --git a/packages/meshbay-hub/tests/test_admin.py b/packages/meshbay-hub/tests/test_admin.py index 51b233a..ad48487 100644 --- a/packages/meshbay-hub/tests/test_admin.py +++ b/packages/meshbay-hub/tests/test_admin.py @@ -5,7 +5,6 @@ Integration tests for the admin/moderation panel API. import pytest from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey - from meshbay_common.crypto import pk_to_b64 from meshbay_hub.api.deps import set_admin_usernames @@ -162,6 +161,44 @@ async def test_admin_change_role(client): @pytest.mark.asyncio +async def test_moderator_cannot_change_roles_or_revoke(client): + """A moderator suspends and restores (reversible); it cannot promote anyone + or hard-revoke, which would be a path from the moderation role to full + instance control.""" + _, admin_token = await _setup_admin(client, "boss") + mod_id = await _register(client, "moduser") + await client.patch(f"/v1/admin/users/{mod_id}", json={"role": "moderator"}, + headers={"Authorization": f"Bearer {admin_token}"}) + mod_token = await _login(client, "moduser") + mod_h = {"Authorization": f"Bearer {mod_token}"} + + victim = await _register(client, "victim", email="v@x.com") + + # No promoting an accomplice. + r = await client.patch(f"/v1/admin/users/{victim}", json={"role": "admin"}, + headers=mod_h) + assert r.status_code == 403 + + # No hard revocation. + r = await client.patch(f"/v1/admin/users/{victim}", json={"status": "revoked"}, + headers=mod_h) + assert r.status_code == 403 + + # No touching an admin's account. + admin2 = await _register(client, "admin2", email="a2@x.com") + await client.patch(f"/v1/admin/users/{admin2}", json={"role": "admin"}, + headers={"Authorization": f"Bearer {admin_token}"}) + r = await client.patch(f"/v1/admin/users/{admin2}", json={"status": "suspended"}, + headers=mod_h) + assert r.status_code == 403 + + # Suspending a plain user is still fine. + r = await client.patch(f"/v1/admin/users/{victim}", json={"status": "suspended"}, + headers=mod_h) + assert r.status_code == 200 + + +@pytest.mark.asyncio async def test_admin_cannot_modify_self(client): admin_id, token = await _setup_admin(client) r = await client.patch(f"/v1/admin/users/{admin_id}", |