Merge pull request #309 from BigBodyCobain/feat/cross-node-dm-mailbox-replication

DM mailbox: per-(sender, recipient) anti-spam cap + replication primitives
This commit is contained in:
Shadowbroker
2026-05-22 22:43:26 -06:00
committed by GitHub
5 changed files with 786 additions and 0 deletions
@@ -0,0 +1,270 @@
"""Per-(sender, recipient) anti-spam cap on the DM relay.
The user-stated rule: a single sender can have at most N UNACKED messages
parked in a single recipient's mailbox at any one time (N=2 by default).
Once the recipient pulls a message, the sender's quota for that pair
frees up.
Network rule, not local rule
-----------------------------
The cap is enforced TWICE:
1. ``DMRelay.deposit(...)`` -- local check on the sender's own node.
Refuses to spool the (N+1)th message before it can be replicated.
2. ``DMRelay.accept_replica(...)`` -- replication-acceptance check on
every receiving peer. Refuses to accept an inbound replica that
would put the local mailbox over the cap, even if the originating
peer claims it had cap room.
The double enforcement matters because cap (1) is client-side -- a
hostile relay could patch it out and continue to spool extras locally.
Cap (2) means those extras can't propagate: every honest peer rejects
them on the way in. A recipient who polls from honest peers therefore
never sees more than N pending from any one sender, regardless of how
many spam attempts the sender's own relay accepted.
These tests pin both halves of the rule.
"""
from __future__ import annotations
import time
import pytest
@pytest.fixture
def relay():
"""Fresh ``DMRelay`` per test."""
from services.mesh.mesh_dm_relay import DMRelay
r = DMRelay()
r._mailboxes.clear()
r._blocks.clear()
r._stats = {"messages_in_memory": 0}
return r
def _deposit(
relay,
*,
sender: str = "alice",
recipient_token: str = "bob_mailbox_token_abc",
ciphertext: str = "ciphertext-blob",
msg_id: str = "",
):
"""Convenience wrapper using ``shared`` delivery class."""
return relay.deposit(
sender_id=sender,
raw_sender_id=sender,
recipient_id="bob",
ciphertext=ciphertext,
msg_id=msg_id,
delivery_class="shared",
recipient_token=recipient_token,
)
# ---------------------------------------------------------------------------
# Local cap on ``deposit``
# ---------------------------------------------------------------------------
class TestDepositCap:
def test_two_deposits_from_same_sender_succeed(self, relay):
r1 = _deposit(relay)
r2 = _deposit(relay)
assert r1["ok"] is True
assert r2["ok"] is True
assert r1["msg_id"] != r2["msg_id"]
def test_third_deposit_from_same_sender_rejected(self, relay):
_deposit(relay)
_deposit(relay)
r3 = _deposit(relay)
assert r3["ok"] is False
detail = r3["detail"].lower()
assert "unread" in detail or "read your messages" in detail
def test_different_senders_have_independent_quotas(self, relay):
for _ in range(2):
assert _deposit(relay, sender="alice")["ok"] is True
for _ in range(2):
assert _deposit(relay, sender="carol")["ok"] is True
assert _deposit(relay, sender="carol")["ok"] is False
def test_different_recipients_have_independent_quotas(self, relay):
for _ in range(2):
assert _deposit(relay, sender="alice", recipient_token="bob_token")["ok"] is True
for _ in range(2):
assert _deposit(relay, sender="alice", recipient_token="dave_token")["ok"] is True
def test_ack_frees_quota(self, relay):
r1 = _deposit(relay)
_deposit(relay)
assert _deposit(relay)["ok"] is False
mailbox_key = relay._hashed_mailbox_token("bob_mailbox_token_abc")
relay._mailboxes[mailbox_key] = [
m for m in relay._mailboxes[mailbox_key]
if m.msg_id != r1["msg_id"]
]
relay._stats["messages_in_memory"] = sum(
len(v) for v in relay._mailboxes.values()
)
r3 = _deposit(relay)
assert r3["ok"] is True, f"expected quota free after ack, got: {r3}"
def test_cap_is_env_tunable(self, relay, monkeypatch):
import services.mesh.mesh_dm_relay as mdr
monkeypatch.setattr(
mdr.DMRelay,
"_per_sender_pending_limit",
lambda self: 1,
)
assert _deposit(relay)["ok"] is True
assert _deposit(relay)["ok"] is False
# ---------------------------------------------------------------------------
# Replication-acceptance cap (the half that makes this a network rule)
# ---------------------------------------------------------------------------
class TestAcceptReplicaCap:
def _envelope(self, *, msg_id: str, sender_block_ref: str, mailbox_key: str):
return {
"msg_id": msg_id,
"mailbox_key": mailbox_key,
"sender_block_ref": sender_block_ref,
"sender_id": "alice",
"sender_seal": "",
"ciphertext": f"ciphertext-{msg_id}",
"timestamp": time.time(),
"delivery_class": "shared",
"relay_salt": "",
"payload_format": "dm1",
"session_welcome": "",
}
def test_replica_accepted_under_cap(self, relay):
env = self._envelope(
msg_id="dm_replica_1",
sender_block_ref="alice_block_ref",
mailbox_key="mailbox_xyz",
)
result = relay.accept_replica(envelope=env)
assert result["ok"] is True
def test_replica_idempotent_on_duplicate_msg_id(self, relay):
mailbox_key = "mailbox_xyz"
env = self._envelope(
msg_id="dm_dup_1",
sender_block_ref="alice_block_ref",
mailbox_key=mailbox_key,
)
r1 = relay.accept_replica(envelope=env)
r2 = relay.accept_replica(envelope=env)
assert r1["ok"] is True
assert r2["ok"] is True
assert r2.get("duplicate") is True
assert len(relay._mailboxes[mailbox_key]) == 1
def test_replica_rejected_when_local_count_already_at_cap(self, relay):
mailbox_key = "mailbox_xyz"
for i in (1, 2):
relay.accept_replica(envelope=self._envelope(
msg_id=f"dm_seeded_{i}",
sender_block_ref="alice_block_ref",
mailbox_key=mailbox_key,
))
result = relay.accept_replica(envelope=self._envelope(
msg_id="dm_overcap_3",
sender_block_ref="alice_block_ref",
mailbox_key=mailbox_key,
))
assert result["ok"] is False
assert result.get("cap_violation") is True
assert result.get("pending") == 2
assert result.get("limit") == 2
assert len(relay._mailboxes[mailbox_key]) == 2
def test_replica_from_different_sender_passes_when_one_is_at_cap(self, relay):
mailbox_key = "mailbox_xyz"
for i in (1, 2):
relay.accept_replica(envelope=self._envelope(
msg_id=f"dm_alice_{i}",
sender_block_ref="alice_block_ref",
mailbox_key=mailbox_key,
))
assert relay.accept_replica(envelope=self._envelope(
msg_id="dm_alice_3",
sender_block_ref="alice_block_ref",
mailbox_key=mailbox_key,
))["ok"] is False
assert relay.accept_replica(envelope=self._envelope(
msg_id="dm_carol_1",
sender_block_ref="carol_block_ref",
mailbox_key=mailbox_key,
))["ok"] is True
def test_replica_rejects_malformed_envelopes(self, relay):
for bad in (
{},
{"msg_id": "x"},
{"msg_id": "x", "mailbox_key": "y"},
"not an object at all",
):
result = relay.accept_replica(envelope=bad)
assert result["ok"] is False
# ---------------------------------------------------------------------------
# ``envelope_for_replication`` -- helper for the outbound replication path
# ---------------------------------------------------------------------------
class TestEnvelopeForReplication:
def test_returns_envelope_for_stored_message(self, relay):
r = _deposit(relay, ciphertext="hello-ciphertext")
msg_id = r["msg_id"]
mailbox_key = relay._hashed_mailbox_token("bob_mailbox_token_abc")
env = relay.envelope_for_replication(mailbox_key=mailbox_key, msg_id=msg_id)
assert env is not None
assert env["msg_id"] == msg_id
assert env["mailbox_key"] == mailbox_key
assert env["ciphertext"] == "hello-ciphertext"
assert env["delivery_class"] == "shared"
for k in ("msg_id", "mailbox_key", "sender_block_ref", "ciphertext"):
assert env.get(k), f"envelope missing required field {k!r}"
def test_returns_none_for_unknown_message(self, relay):
env = relay.envelope_for_replication(
mailbox_key="never_existed", msg_id="never_existed",
)
assert env is None
def test_envelope_round_trips_through_accept_replica(self, relay):
from services.mesh.mesh_dm_relay import DMRelay
receiver_relay = DMRelay()
receiver_relay._mailboxes.clear()
receiver_relay._stats = {"messages_in_memory": 0}
r = _deposit(relay)
msg_id = r["msg_id"]
mailbox_key = relay._hashed_mailbox_token("bob_mailbox_token_abc")
env = relay.envelope_for_replication(
mailbox_key=mailbox_key, msg_id=msg_id,
)
assert env is not None
result = receiver_relay.accept_replica(envelope=env)
assert result["ok"] is True
stored = receiver_relay._mailboxes.get(mailbox_key, [])
assert len(stored) == 1
assert stored[0].msg_id == msg_id
assert stored[0].ciphertext == "ciphertext-blob"
@@ -0,0 +1,150 @@
"""POST /api/mesh/dm/replicate-envelope — receiving side of cross-node DM
mailbox replication.
This is the endpoint that peer relays call when they want to hand off an
encrypted DM envelope to us (so the recipient can log into our node and
find their messages). It re-enforces the per-(sender, recipient) anti-spam
cap so hostile sender relays can't widen the cap by skipping the local
check on their own deposit path.
The endpoint:
* authenticates the caller via the existing per-peer HMAC pattern
(same one /api/mesh/infonet/peer-push and /api/mesh/gate/peer-push
use, introduced in #256 — ``X-Peer-Url`` + ``X-Peer-HMAC`` headers
keyed off ``resolve_peer_key_for_url``)
* rejects bodies > 64 KB (DM envelope size is bounded by
``MESH_DM_MAX_MSG_BYTES`` — 64KB ceiling has generous headroom)
* rejects requests without a valid peer HMAC with 403
* passes the envelope to ``DMRelay.accept_replica`` which enforces
the cap
This file pins the endpoint contract. The cap enforcement itself is
tested in ``test_dm_relay_per_sender_cap.py`` against the relay's
``accept_replica`` method directly.
"""
from __future__ import annotations
import asyncio
import hashlib
import hmac
import json
import pytest
from httpx import ASGITransport, AsyncClient
@pytest.fixture
def remote_client():
"""ASGI client with peer IP 1.2.3.4 — never on the local-operator
allowlist. Used to prove the endpoint isn't accidentally reachable
by random remote callers without peer HMAC."""
from main import app
class _RemoteClient:
def __init__(self):
self._loop = asyncio.new_event_loop()
self._transport = ASGITransport(app=app, client=("1.2.3.4", 12345))
self._base = "http://1.2.3.4:8000"
def post(self, url, **kw):
async def go():
async with AsyncClient(transport=self._transport, base_url=self._base) as ac:
return await ac.post(url, **kw)
return self._loop.run_until_complete(go())
def close(self):
self._loop.close()
c = _RemoteClient()
yield c
c.close()
class TestReplicateEndpointAuth:
def test_rejects_request_without_peer_hmac(self, remote_client):
"""A peer push that does NOT carry X-Peer-Url + X-Peer-HMAC
must be rejected with 403 before the envelope is ever passed
to the relay. Same gate the existing infonet/gate peer-push
endpoints enforce."""
payload = {
"envelope": {
"msg_id": "dm_unauth_1",
"mailbox_key": "mb",
"sender_block_ref": "sender",
"ciphertext": "x",
},
}
r = remote_client.post(
"/api/mesh/dm/replicate-envelope",
json=payload,
)
assert r.status_code == 403
assert "peer HMAC" in r.text or "peer hmac" in r.text.lower()
def test_rejects_wrong_peer_hmac(self, remote_client, monkeypatch):
"""A request with a peer HMAC header keyed off the WRONG secret
is rejected. Confirms the HMAC is actually verified — a tampered
body or a key-substitution attack doesn't sneak through."""
# Plant a known peer secret. The request will sign with a
# DIFFERENT key, so verification must fail.
from services.config import get_settings
monkeypatch.setenv("MESH_PEER_PUSH_SECRET", "real-secret-32-chars-min-padding-padding")
get_settings.cache_clear()
body = json.dumps({
"envelope": {
"msg_id": "dm_wronghmac",
"mailbox_key": "mb",
"sender_block_ref": "sender",
"ciphertext": "x",
},
}).encode("utf-8")
wrong_hmac = hmac.new(b"wrong-key", body, hashlib.sha256).hexdigest()
r = remote_client.post(
"/api/mesh/dm/replicate-envelope",
content=body,
headers={
"Content-Type": "application/json",
"X-Peer-Url": "http://example-peer.onion:8000",
"X-Peer-HMAC": wrong_hmac,
},
)
assert r.status_code == 403
def test_rejects_oversize_body(self, remote_client):
"""64 KB ceiling — anything bigger doesn't even get parsed.
Defends against memory amplification via giant ciphertexts."""
# 100 KB body is well over the 64 KB cap.
big = b"{" + b"x" * 100_000 + b"}"
r = remote_client.post(
"/api/mesh/dm/replicate-envelope",
content=big,
headers={
"Content-Type": "application/json",
"Content-Length": str(len(big)),
},
)
assert r.status_code in (400, 413), (
f"oversize body should be rejected with 400/413, got {r.status_code}"
)
class TestReplicateEndpointRegistered:
def test_route_present_in_app(self):
"""Static check that the route is actually wired into the app.
Catches a future refactor that drops the router include or
deletes the endpoint by accident."""
from main import app
paths_methods = set()
for route in app.routes:
path = getattr(route, "path", None)
methods = getattr(route, "methods", set()) or set()
for m in methods:
paths_methods.add((m, path))
assert ("POST", "/api/mesh/dm/replicate-envelope") in paths_methods, (
"POST /api/mesh/dm/replicate-envelope is not registered on the app"
)