diff options
Diffstat (limited to 'packages/meshbay-node/tests/test_security_regressions.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_security_regressions.py | 97 |
1 files changed, 16 insertions, 81 deletions
diff --git a/packages/meshbay-node/tests/test_security_regressions.py b/packages/meshbay-node/tests/test_security_regressions.py index 43e88c2..26a3b1d 100644 --- a/packages/meshbay-node/tests/test_security_regressions.py +++ b/packages/meshbay-node/tests/test_security_regressions.py @@ -842,14 +842,11 @@ def test_node_control_api_serves_no_html(): # ── NS4 / M3: the node's own controls take no authority from the hub ───────── -async def _owner_session(tmp_path, roster, *, device: str = "", - confirmed: bool = False): +async def _owner_session(tmp_path, roster): """A session whose token says it is the account this node belongs to. Which is all a hub can decide: `_user_id` is the `sub` of a JWT it issued, - so this is what an active hub forging a token arrives holding. Whether the - *device* on the connection is one the node pinned as an operator is the - other half, and no token can assert it. + so this is what an active hub forging a token arrives holding. """ from meshbay_node.indexer.group_index import GroupIndex @@ -865,9 +862,6 @@ async def _owner_session(tmp_path, roster, *, device: str = "", } session._group_id = "g" * 32 session._user_id = "the-owner" - session._username = "the-owner" - session._pinned_pk = device - session._device_confirmed = confirmed session._pk_user = "" session.sent = [] session._send = session.sent.append @@ -876,18 +870,18 @@ async def _owner_session(tmp_path, roster, *, device: str = "", @pytest.mark.asyncio -async def test_a_token_naming_the_owner_is_not_node_authority(tmp_path): +@pytest.mark.parametrize("mtype", ["node_status", "node_settings_set", "roster_read", + "denylist_read", "denylist_clear", "node_reload"]) +async def test_a_token_naming_the_owner_reaches_no_node_control(tmp_path, mtype): """ - The hub holds no user keys, so it cannot countersign a device — but it does - choose what a token says. Six node-wide controls used to be gated on the - account id alone, which is the hub's to decide: `node_status` (every group - on the machine, with the operator's absolute paths), `node_settings_set`, - `roster_read`, `denylist_read`, `denylist_clear` (the persisted revocation - H4 exists to keep) and `node_reload`. - - An active hub reaches a completed handshake wherever it can also obtain the - group key, which §3.5 concedes it can in an open-join group. From there, - "the hub says you are the owner" was the whole of the check. + The hub holds no user keys, but it does choose what a token says. Six + node-wide controls were once gated on the account id alone: `node_status` + (every group on the machine, with the operator's absolute paths), + `node_settings_set`, `roster_read`, `denylist_read`, `denylist_clear` (the + persisted revocation H4 exists to keep) and `node_reload`. A device proof + closed that; MNP 6.0 removed the messages, since no client sent them. They + are the loopback API's and the CLI's — on the operator's own machine — and a + peer naming one over MNP is answered by nobody. """ from meshbay_node.roster import Roster @@ -895,67 +889,8 @@ async def test_a_token_naming_the_owner_is_not_node_authority(tmp_path): await roster.open() try: forged = await _owner_session(tmp_path, roster) - await forged._do_node_status({}) - assert forged.sent[-1]["type"] == "error" - assert forged.sent[-1]["code"] == "not_operator" - - forged.sent.clear() - await forged._do_denylist_read({}) - assert forged.sent[-1]["type"] == "error" - - forged.sent.clear() - await forged._do_denylist_clear({"subject": ""}) - assert forged.sent[-1]["type"] == "error", ( - "clearing the denylist undoes a revocation every node enforces") - finally: - await roster.close() - - -@pytest.mark.asyncio -async def test_an_identified_device_that_is_not_an_operator_is_refused(tmp_path): - """Being a pinned member of the group is not being the node's operator. - - The device proof is real here; what it proves is an ordinary member's key, - and `operator_pks()` is rebuilt from the roster on every call so a revoked - one stops working at once. - """ - from meshbay_common.crypto import pk_to_b64 - from meshbay_node.roster import Roster - - roster = Roster(db_path=tmp_path / "roster.db") - await roster.open() - try: - sk = Ed25519PrivateKey.generate() - member_pk = pk_to_b64(sk.public_key()) - await roster.pin_identity("the-owner", "the-owner", member_pk, - member_pk, "code") - session = await _owner_session(tmp_path, roster, device=member_pk, - confirmed=True) - await session._do_node_status({}) - assert session.sent[-1]["type"] == "error" - finally: - await roster.close() - - -@pytest.mark.asyncio -async def test_a_paired_operator_device_is_what_opens_it(tmp_path): - """The positive case, so the test above is about authority and not about - everything being refused.""" - from meshbay_common.crypto import pk_to_b64 - from meshbay_common.join import ROLE_OPERATOR - from meshbay_node.roster import Roster - - roster = Roster(db_path=tmp_path / "roster.db") - await roster.open() - try: - sk = Ed25519PrivateKey.generate() - pk = pk_to_b64(sk.public_key()) - await roster.pin_identity("the-owner", "the-owner", pk, pk, "code") - await roster.set_member("", "the-owner", ROLE_OPERATOR, "active", - "local-cli") - session = await _owner_session(tmp_path, roster, device=pk, - confirmed=True) - await session._do_denylist_read({}) - assert session.sent[-1]["type"] != "error", session.sent[-1] + forged._dispatch_message({"type": mtype, "subject": "", "group_id": "g" * 32, + "settings": {"max_peers": 1}}) + assert forged.sent == [], f"{mtype} answered over MNP: {forged.sent}" finally: await roster.close() |