diff options
Diffstat (limited to 'packages/meshbay-node/tests/test_security_regressions.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_security_regressions.py | 44 |
1 files changed, 22 insertions, 22 deletions
diff --git a/packages/meshbay-node/tests/test_security_regressions.py b/packages/meshbay-node/tests/test_security_regressions.py index fa55ff6..e203811 100644 --- a/packages/meshbay-node/tests/test_security_regressions.py +++ b/packages/meshbay-node/tests/test_security_regressions.py @@ -174,7 +174,7 @@ def _session(tmp_path: Path, user_id: str) -> WebRTCPeerSession: return session -def test_upload_cannot_overwrite_another_members_file(tmp_path): +async def test_upload_cannot_overwrite_another_members_file(tmp_path): """ C5a: uploads used to land in the shared root under a client-chosen name and overwrite whatever was there. That let any member destroy the operator's @@ -192,7 +192,7 @@ def test_upload_cannot_overwrite_another_members_file(tmp_path): original.write_bytes(b"operator's original content") attacker = _session(tmp_path, "attacker-user") - attacker._do_file_upload(sealed_upload( + await attacker._do_file_upload(sealed_upload( attacker, filename="important.mp4", data=b"attacker content")) assert original.read_bytes() == b"operator's original content", ( @@ -200,18 +200,18 @@ def test_upload_cannot_overwrite_another_members_file(tmp_path): assert (uploads / "important (2).mp4").read_bytes() == b"attacker content" -def test_upload_second_attempt_cannot_replace_own_completed_file(tmp_path): +async def test_upload_second_attempt_cannot_replace_own_completed_file(tmp_path): """C5a: even the original uploader does not get to overwrite.""" session = _session(tmp_path, "user-1") - def _send_it(): + async def _send_it(): # Sealed afresh each time: a nonce is drawn per message, so re-sending # the same dict would be a replay rather than a second upload. - session._do_file_upload(sealed_upload( + await session._do_file_upload(sealed_upload( session, filename="movie.mp4", data=b"first")) - _send_it() + await _send_it() session.sent.clear() - _send_it() + await _send_it() uploads = _uploads_dir(session) assert (uploads / "movie.mp4").read_bytes() == b"first", ( "the first upload was replaced") @@ -236,7 +236,7 @@ def test_dir_create_cannot_escape_the_shared_root(tmp_path, bad): assert set(tmp_path.rglob("*")) == before, f"created something via {bad!r}" -def test_the_client_names_a_folder_and_never_a_filesystem_path(tmp_path): +async def test_the_client_names_a_folder_and_never_a_filesystem_path(tmp_path): """ The destination is now the folder the sender is looking at, which means the client does choose it — and the whole of what keeps that safe is that the @@ -255,7 +255,7 @@ def test_the_client_names_a_folder_and_never_a_filesystem_path(tmp_path): for bad in ("../../etc", "/etc", "shared/../..", "shared/../../etc", "nope", "shared/missing"): session.sent.clear() - session._do_file_upload(sealed_upload( + await session._do_file_upload(sealed_upload( session, filename="note.txt", data=b"x", dir=bad)) refusal = [m for m in session.sent if m.get("type") == "error"] assert refusal, f"{bad!r} was accepted" @@ -264,7 +264,7 @@ def test_the_client_names_a_folder_and_never_a_filesystem_path(tmp_path): assert set(tmp_path.rglob("*")) == before, "a refused upload still wrote" -def test_an_upload_lands_in_the_folder_it_names(tmp_path): +async def test_an_upload_lands_in_the_folder_it_names(tmp_path): """ And in that folder itself — the `uploads/` subdirectory the node used to create is gone. Somebody dropping a file into the folder they are looking @@ -274,7 +274,7 @@ def test_an_upload_lands_in_the_folder_it_names(tmp_path): root = session._ctx["roots"].roots[0] (root.path / "Albums").mkdir() - session._do_file_upload(sealed_upload( + await session._do_file_upload(sealed_upload( session, filename="note.txt", data=b"x", dir=f"{root.name}/Albums")) assert (root.path / "Albums" / "note.txt").read_bytes() == b"x" @@ -283,7 +283,7 @@ def test_an_upload_lands_in_the_folder_it_names(tmp_path): assert not (root.path / "uploads").exists() -def test_an_upload_goes_to_the_root_it_names(tmp_path): +async def test_an_upload_goes_to_the_root_it_names(tmp_path): """ With two writable roots there is no defensible default, and the client is the only party that knows which directory the person is looking at. The @@ -301,14 +301,14 @@ def test_an_upload_goes_to_the_root_it_names(tmp_path): {"path": str(incoming), "writable": True}, ]) - session._do_file_upload(sealed_upload( + await session._do_file_upload(sealed_upload( session, filename="note.txt", data=b"x", dir="Incoming")) assert (incoming / "note.txt").read_bytes() == b"x" assert not (media / "note.txt").exists(), "it went to the first root instead" -def test_a_read_only_root_refuses_an_upload(tmp_path): +async def test_a_read_only_root_refuses_an_upload(tmp_path): """ RO is the mechanism now, not a hidden button. It binds the operator too: "read-only for everyone" is what makes a published library one, and an @@ -321,7 +321,7 @@ def test_a_read_only_root_refuses_an_upload(tmp_path): session._ctx["roots"] = RootSet.build([{"path": str(published)}]) session._is_node_admin = lambda: True - session._do_file_upload(sealed_upload( + await session._do_file_upload(sealed_upload( session, filename="note.txt", data=b"x", dir="Published")) refusal = [m for m in session.sent if m.get("type") == "error"] @@ -329,7 +329,7 @@ def test_a_read_only_root_refuses_an_upload(tmp_path): assert not (published / "note.txt").exists() -def test_a_fully_read_only_group_refuses_an_unaddressed_upload(tmp_path): +async def test_a_fully_read_only_group_refuses_an_unaddressed_upload(tmp_path): """ An MNP 1.0 client names no root, so the node falls back to the first writable one. There isn't one here, and the fallback must refuse rather @@ -340,7 +340,7 @@ def test_a_fully_read_only_group_refuses_an_unaddressed_upload(tmp_path): session = _session(tmp_path, "user-1") session._ctx["roots"] = RootSet.build([{"path": str(published)}]) - session._do_file_upload(sealed_upload( + await session._do_file_upload(sealed_upload( session, filename="note.txt", data=b"x")) refusal = [m for m in session.sent if m.get("type") == "error"] @@ -348,7 +348,7 @@ def test_a_fully_read_only_group_refuses_an_unaddressed_upload(tmp_path): assert not (published / "note.txt").exists() -def test_an_ejected_root_refuses_an_upload(tmp_path): +async def test_an_ejected_root_refuses_an_upload(tmp_path): """ Writing to a drive somebody has their hand on is the thing eject exists to stop. `writable` is still true — that is configuration — so availability @@ -363,7 +363,7 @@ def test_an_ejected_root_refuses_an_upload(tmp_path): roots.roots[0].available = False session._ctx["roots"] = roots - session._do_file_upload(sealed_upload( + await session._do_file_upload(sealed_upload( session, filename="note.txt", data=b"x", dir="USB")) refusal = [m for m in session.sent if m.get("type") == "error"] @@ -371,19 +371,19 @@ def test_an_ejected_root_refuses_an_upload(tmp_path): assert not (usb / "note.txt").exists() -def test_two_members_can_send_the_same_filename(tmp_path): +async def test_two_members_can_send_the_same_filename(tmp_path): """ One shared uploads/ means collisions are ordinary — every camera produces IMG_1234.jpg. The second gets a free name; neither replaces the other. """ first = _session(tmp_path, "user-1") - first._do_file_upload(sealed_upload( + await first._do_file_upload(sealed_upload( first, filename="IMG_1234.jpg", data=b"first")) second = _session(tmp_path, "user-2") # Same group, so the same key: `_session` builds one per call, and two # members of one group do not have two. second._ctx["gek"] = first._ctx["gek"] - second._do_file_upload(sealed_upload( + await second._do_file_upload(sealed_upload( second, filename="IMG_1234.jpg", data=b"second")) uploads = _uploads_dir(first) |