mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-27 09:32:28 +02:00
Harden infonet control surfaces
This commit is contained in:
@@ -5,7 +5,7 @@ from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
|
||||
|
||||
def _request(path: str, method: str = "POST") -> Request:
|
||||
def _request(path: str, method: str = "POST", query_string: bytes = b"") -> Request:
|
||||
return Request(
|
||||
{
|
||||
"type": "http",
|
||||
@@ -13,6 +13,7 @@ def _request(path: str, method: str = "POST") -> Request:
|
||||
"client": ("test", 12345),
|
||||
"method": method,
|
||||
"path": path,
|
||||
"query_string": query_string,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -504,6 +505,61 @@ def test_private_infonet_gate_write_returns_preparing_state_when_wormhole_not_re
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
def test_invite_scoped_prekey_lookup_reaches_handler_while_lane_prepares(monkeypatch):
|
||||
"""Copied-address import must not be blocked by private-lane warmup."""
|
||||
import main
|
||||
import auth
|
||||
from services.config import get_settings
|
||||
from services import wormhole_supervisor
|
||||
|
||||
monkeypatch.setenv("MESH_PRIVATE_CLEARNET_FALLBACK", "block")
|
||||
monkeypatch.setenv("MESH_BLOCK_LEGACY_NODE_ID_COMPAT", "true")
|
||||
monkeypatch.setenv("MESH_BLOCK_LEGACY_AGENT_ID_LOOKUP", "true")
|
||||
monkeypatch.setenv("MESH_ALLOW_COMPAT_DM_INVITE_IMPORT", "false")
|
||||
get_settings.cache_clear()
|
||||
monkeypatch.setattr(
|
||||
auth,
|
||||
"_anonymous_mode_state",
|
||||
lambda: {
|
||||
"enabled": False,
|
||||
"wormhole_enabled": True,
|
||||
"ready": False,
|
||||
"effective_transport": "direct",
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
wormhole_supervisor,
|
||||
"get_wormhole_state",
|
||||
lambda: {
|
||||
"configured": True,
|
||||
"ready": False,
|
||||
"rns_ready": False,
|
||||
"arti_ready": False,
|
||||
},
|
||||
)
|
||||
|
||||
called = {"value": False}
|
||||
|
||||
async def call_next(_request: Request) -> Response:
|
||||
called["value"] = True
|
||||
return Response(status_code=200)
|
||||
|
||||
response = asyncio.run(
|
||||
main.enforce_high_privacy_mesh(
|
||||
_request(
|
||||
"/api/mesh/dm/prekey-bundle",
|
||||
method="GET",
|
||||
query_string=b"lookup_token=invite-handle",
|
||||
),
|
||||
call_next,
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert called["value"] is True
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
def test_private_dm_send_blocks_at_transitional_tier(monkeypatch):
|
||||
import main
|
||||
import auth
|
||||
|
||||
@@ -47,6 +47,11 @@ def test_infonet_ingest_accepts_valid_event(tmp_path, monkeypatch):
|
||||
|
||||
assert result["accepted"] == 1
|
||||
assert inf.head_hash == evt.event_id
|
||||
info = inf.get_info()
|
||||
assert info["known_nodes"] == 1
|
||||
assert info["author_nodes"] == 1
|
||||
assert info["total_events"] == 1
|
||||
assert info["event_types"]["message"] == 1
|
||||
|
||||
|
||||
def test_verify_node_binding_accepts_current_and_compat_ids_only(monkeypatch):
|
||||
@@ -64,6 +69,8 @@ def test_verify_node_binding_accepts_current_and_compat_ids_only(monkeypatch):
|
||||
f"{current[len(mesh_crypto.NODE_ID_PREFIX):len(mesh_crypto.NODE_ID_PREFIX) + 8]}"
|
||||
)
|
||||
|
||||
monkeypatch.setenv("MESH_DEV_ALLOW_LEGACY_COMPAT", "true")
|
||||
monkeypatch.setenv("MESH_BLOCK_LEGACY_NODE_ID_COMPAT", "false")
|
||||
monkeypatch.setenv("MESH_ALLOW_LEGACY_NODE_ID_COMPAT_UNTIL", "2099-01-01")
|
||||
from services.config import get_settings
|
||||
|
||||
@@ -98,7 +105,7 @@ def test_infonet_append_rejects_missing_signature_fields(tmp_path, monkeypatch):
|
||||
assert "signature" in str(exc).lower()
|
||||
|
||||
|
||||
def test_infonet_load_fails_closed_on_hash_mismatch(tmp_path, monkeypatch):
|
||||
def test_infonet_load_quarantines_and_resets_on_hash_mismatch(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(mesh_hashchain, "DATA_DIR", tmp_path)
|
||||
monkeypatch.setattr(mesh_hashchain, "CHAIN_FILE", tmp_path / "infonet.json")
|
||||
|
||||
@@ -135,8 +142,12 @@ def test_infonet_load_fails_closed_on_hash_mismatch(tmp_path, monkeypatch):
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="Hash mismatch on event load"):
|
||||
mesh_hashchain.Infonet()
|
||||
inf = mesh_hashchain.Infonet()
|
||||
|
||||
assert inf.events == []
|
||||
assert inf.head_hash == mesh_hashchain.GENESIS_HASH
|
||||
assert not mesh_hashchain.CHAIN_FILE.exists()
|
||||
assert list(tmp_path.glob("infonet.json.quarantine.*"))
|
||||
|
||||
|
||||
def test_validate_gate_message_payload_rejects_plaintext_shape():
|
||||
|
||||
@@ -12,6 +12,7 @@ Tests verify:
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import time
|
||||
|
||||
from services.config import get_settings
|
||||
@@ -611,6 +612,99 @@ class TestFetchPrekeyBundleByLookup:
|
||||
"peer prekey lookup unavailable",
|
||||
}
|
||||
|
||||
def test_fetch_lookup_token_uses_bootstrap_peer_without_agent_id(self, tmp_path, monkeypatch):
|
||||
"""Invite lookup can resolve through bootstrap peers without exposing agent_id."""
|
||||
_isolated_relay(tmp_path, monkeypatch)
|
||||
record = _valid_bundle_record("test-agent")
|
||||
requested_urls: list[str] = []
|
||||
|
||||
monkeypatch.setenv("MESH_BOOTSTRAP_SEED_PEERS", "https://seed.example")
|
||||
monkeypatch.setenv("MESH_DEFAULT_SYNC_PEERS", "")
|
||||
monkeypatch.setenv("MESH_RELAY_PEERS", "")
|
||||
get_settings.cache_clear()
|
||||
|
||||
class _Response:
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *_args):
|
||||
return False
|
||||
|
||||
def read(self, _limit: int = -1):
|
||||
return json.dumps(
|
||||
{
|
||||
"ok": True,
|
||||
"identity_dh_pub_key": record["dh_pub_key"],
|
||||
"dh_algo": record["dh_algo"],
|
||||
"public_key": record["public_key"],
|
||||
"public_key_algo": record["public_key_algo"],
|
||||
"protocol_version": record["protocol_version"],
|
||||
"sequence": 1,
|
||||
"signed_at": int(record["bundle"].get("signed_at", 0) or 0),
|
||||
"bundle": record["bundle"],
|
||||
}
|
||||
).encode("utf-8")
|
||||
|
||||
def _urlopen(request, timeout=0):
|
||||
requested_urls.append(str(getattr(request, "full_url", "")))
|
||||
return _Response()
|
||||
|
||||
monkeypatch.setattr("services.mesh.mesh_wormhole_prekey.urllib.request.urlopen", _urlopen)
|
||||
|
||||
from services.mesh.mesh_wormhole_prekey import fetch_dm_prekey_bundle
|
||||
|
||||
result = fetch_dm_prekey_bundle(agent_id="", lookup_token="bootstrap-handle")
|
||||
|
||||
assert result["ok"] is True
|
||||
assert result["agent_id"] == record["agent_id"]
|
||||
assert result["lookup_mode"] == "invite_lookup_handle"
|
||||
assert result["public_lookup"] is True
|
||||
assert requested_urls
|
||||
assert "lookup_token=bootstrap-handle" in requested_urls[0]
|
||||
assert "agent_id" not in requested_urls[0]
|
||||
|
||||
def test_fetch_lookup_token_does_not_parse_peer_pending_as_bundle(self, tmp_path, monkeypatch):
|
||||
"""A peer's private-lane pending response is not a malformed prekey bundle."""
|
||||
_isolated_relay(tmp_path, monkeypatch)
|
||||
requested_urls: list[str] = []
|
||||
|
||||
monkeypatch.setenv("MESH_BOOTSTRAP_SEED_PEERS", "https://seed.example")
|
||||
monkeypatch.setenv("MESH_DEFAULT_SYNC_PEERS", "")
|
||||
monkeypatch.setenv("MESH_RELAY_PEERS", "")
|
||||
get_settings.cache_clear()
|
||||
|
||||
class _Response:
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *_args):
|
||||
return False
|
||||
|
||||
def read(self, _limit: int = -1):
|
||||
return json.dumps(
|
||||
{
|
||||
"ok": True,
|
||||
"pending": True,
|
||||
"status": "preparing_private_lane",
|
||||
"detail": "transport tier insufficient",
|
||||
}
|
||||
).encode("utf-8")
|
||||
|
||||
def _urlopen(request, timeout=0):
|
||||
requested_urls.append(str(getattr(request, "full_url", "")))
|
||||
return _Response()
|
||||
|
||||
monkeypatch.setattr("services.mesh.mesh_wormhole_prekey.urllib.request.urlopen", _urlopen)
|
||||
|
||||
from services.mesh.mesh_wormhole_prekey import fetch_dm_prekey_bundle
|
||||
|
||||
result = fetch_dm_prekey_bundle(agent_id="", lookup_token="bootstrap-handle")
|
||||
|
||||
assert requested_urls
|
||||
assert result["ok"] is False
|
||||
assert result["detail"] == "peer prekey lookup still preparing"
|
||||
assert result["detail"] != "Prekey bundle missing signing key"
|
||||
|
||||
def test_fetch_agent_id_uses_pinned_contact_lookup_handle(self, tmp_path, monkeypatch):
|
||||
"""Pinned invite lookup handle is used before direct agent_id lookup."""
|
||||
relay = _isolated_relay(tmp_path, monkeypatch)
|
||||
|
||||
@@ -71,6 +71,38 @@ def _fresh_wormhole_state(tmp_path, monkeypatch):
|
||||
return relay, mesh_wormhole_identity, mesh_wormhole_contacts, mesh_wormhole_prekey
|
||||
|
||||
|
||||
def test_register_wormhole_dm_key_repairs_missing_local_dh_material(tmp_path, monkeypatch):
|
||||
relay, identity_mod, _contacts_mod, _prekey_mod = _fresh_wormhole_state(tmp_path, monkeypatch)
|
||||
identity = identity_mod.read_wormhole_identity()
|
||||
original_node_id = identity["node_id"]
|
||||
original_public_key = identity["public_key"]
|
||||
original_private_key = identity["private_key"]
|
||||
|
||||
identity_mod.write_dm_identity(
|
||||
{
|
||||
**identity,
|
||||
"dh_pub_key": "",
|
||||
"dh_private_key": "",
|
||||
"bundle_fingerprint": "",
|
||||
"bundle_sequence": 0,
|
||||
"bundle_registered_at": 0,
|
||||
}
|
||||
)
|
||||
|
||||
registered = identity_mod.register_wormhole_dm_key()
|
||||
repaired = identity_mod.read_wormhole_identity()
|
||||
|
||||
assert registered["ok"] is True
|
||||
assert registered["dh_pub_key"]
|
||||
assert registered["dh_algo"] == "X25519"
|
||||
assert repaired["dh_pub_key"] == registered["dh_pub_key"]
|
||||
assert repaired["dh_private_key"]
|
||||
assert repaired["node_id"] == original_node_id
|
||||
assert repaired["public_key"] == original_public_key
|
||||
assert repaired["private_key"] == original_private_key
|
||||
assert relay.get_dh_key(original_node_id)["dh_pub_key"] == registered["dh_pub_key"]
|
||||
|
||||
|
||||
def _export_verified_invite(identity_mod):
|
||||
exported = identity_mod.export_wormhole_dm_invite()
|
||||
assert exported["ok"] is True
|
||||
@@ -460,6 +492,30 @@ def test_imported_dm_invite_pins_contact_as_invite_pinned(tmp_path, monkeypatch)
|
||||
assert contacts_mod.list_wormhole_dm_contacts()[imported["peer_id"]]["trust_level"] == "invite_pinned"
|
||||
|
||||
|
||||
def test_imported_dm_invite_saves_pending_contact_when_prekey_not_visible(tmp_path, monkeypatch):
|
||||
_relay, identity_mod, contacts_mod, prekey_mod = _fresh_wormhole_state(tmp_path, monkeypatch)
|
||||
exported, verified = _export_verified_invite(identity_mod)
|
||||
monkeypatch.setattr(
|
||||
prekey_mod,
|
||||
"fetch_dm_prekey_bundle",
|
||||
lambda **_kw: {"ok": False, "detail": "Prekey bundle not found"},
|
||||
)
|
||||
|
||||
imported = identity_mod.import_wormhole_dm_invite(exported["invite"], alias="alice")
|
||||
contact = imported["contact"]
|
||||
|
||||
assert imported["ok"] is True
|
||||
assert imported["pending_prekey"] is True
|
||||
assert imported["peer_id"] == verified["peer_id"]
|
||||
assert contact["alias"] == "alice"
|
||||
assert contact["trust_level"] == "invite_pinned"
|
||||
assert contact["invitePinnedPrekeyLookupHandle"] == exported["invite"]["payload"]["prekey_lookup_handle"]
|
||||
assert contact["remotePrekeyLookupMode"] == "invite_lookup_handle"
|
||||
assert contact["remotePrekeyFingerprint"] == verified["trust_fingerprint"]
|
||||
assert contact["dhPubKey"] == ""
|
||||
assert contacts_mod.list_wormhole_dm_contacts()[verified["peer_id"]]["trust_level"] == "invite_pinned"
|
||||
|
||||
|
||||
def test_imported_dm_invite_requires_root_attested_prekey_bundle(tmp_path, monkeypatch):
|
||||
relay, identity_mod, _contacts_mod, _prekey_mod = _fresh_wormhole_state(tmp_path, monkeypatch)
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
"""Regression coverage for operator-only control surfaces."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("method", "path", "payload"),
|
||||
[
|
||||
("get", "/api/wormhole/identity", None),
|
||||
("post", "/api/wormhole/identity/bootstrap", {}),
|
||||
("post", "/api/wormhole/gate/enter", {"gate_id": "general-talk"}),
|
||||
("post", "/api/wormhole/gate/leave", {"gate_id": "general-talk"}),
|
||||
("post", "/api/wormhole/sign", {"event_type": "gate_event", "payload": {"ok": True}}),
|
||||
("post", "/api/wormhole/gate/key/rotate", {"gate_id": "general-talk", "reason": "test"}),
|
||||
(
|
||||
"post",
|
||||
"/api/wormhole/gate/key/grant",
|
||||
{
|
||||
"gate_id": "general-talk",
|
||||
"recipient_node_id": "node-test",
|
||||
"recipient_dh_pub": "dh-test",
|
||||
},
|
||||
),
|
||||
("post", "/api/wormhole/gate/persona/create", {"gate_id": "general-talk", "label": "test"}),
|
||||
(
|
||||
"post",
|
||||
"/api/wormhole/gate/persona/activate",
|
||||
{"gate_id": "general-talk", "persona_id": "persona-test"},
|
||||
),
|
||||
("post", "/api/wormhole/gate/persona/clear", {"gate_id": "general-talk"}),
|
||||
(
|
||||
"post",
|
||||
"/api/wormhole/gate/persona/retire",
|
||||
{"gate_id": "general-talk", "persona_id": "persona-test"},
|
||||
),
|
||||
(
|
||||
"post",
|
||||
"/api/wormhole/gate/message/sign-encrypted",
|
||||
{
|
||||
"gate_id": "general-talk",
|
||||
"epoch": 1,
|
||||
"ciphertext": "ciphertext",
|
||||
"nonce": "nonce",
|
||||
"format": "mls1",
|
||||
"envelope_hash": "hash",
|
||||
},
|
||||
),
|
||||
("post", "/api/wormhole/gate/message/compose", {"gate_id": "general-talk", "plaintext": "hello"}),
|
||||
("post", "/api/wormhole/sign-raw", {"message": "raw"}),
|
||||
("post", "/api/wormhole/gate/state/export", {"gate_id": "general-talk"}),
|
||||
("post", "/api/wormhole/gate/proof", {"gate_id": "general-talk"}),
|
||||
("post", "/api/wormhole/connect", {}),
|
||||
("post", "/api/layers", {"layers": {"viirs_nightlights": True}}),
|
||||
("post", "/api/ais/feed", {"msgs": []}),
|
||||
],
|
||||
)
|
||||
def test_remote_control_surface_rejects_without_local_operator_or_admin(
|
||||
remote_client, method, path, payload
|
||||
):
|
||||
request = getattr(remote_client, method)
|
||||
response = request(path, json=payload) if payload is not None else request(path)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_remote_agent_actions_poll_rejects_without_local_operator_or_admin(remote_client):
|
||||
response = remote_client.get("/api/ai/agent-actions")
|
||||
|
||||
assert response.status_code == 403
|
||||
@@ -0,0 +1,52 @@
|
||||
"""CrowdThreat ingestion is operator opt-in only."""
|
||||
|
||||
|
||||
class _CrowdThreatResponse:
|
||||
status_code = 200
|
||||
|
||||
def json(self):
|
||||
return {
|
||||
"data": {
|
||||
"threats": [
|
||||
{
|
||||
"id": "ct-1",
|
||||
"title": "Example report",
|
||||
"location": {
|
||||
"lng_lat": [12.5, 41.9],
|
||||
"name": "Example place",
|
||||
"country": {"name": "Italy"},
|
||||
},
|
||||
"category": {"id": 1, "name": "Security"},
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_crowdthreat_disabled_by_default_does_not_call_upstream(monkeypatch):
|
||||
from services.fetchers import _store, crowdthreat
|
||||
|
||||
monkeypatch.delenv("CROWDTHREAT_ENABLED", raising=False)
|
||||
monkeypatch.setitem(_store.latest_data, "crowdthreat", [{"id": "old"}])
|
||||
monkeypatch.setattr(
|
||||
crowdthreat,
|
||||
"fetch_with_curl",
|
||||
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("upstream called")),
|
||||
)
|
||||
|
||||
crowdthreat.fetch_crowdthreat()
|
||||
|
||||
assert _store.latest_data["crowdthreat"] == []
|
||||
|
||||
|
||||
def test_crowdthreat_opt_in_fetches_when_layer_is_enabled(monkeypatch):
|
||||
from services.fetchers import _store, crowdthreat
|
||||
|
||||
monkeypatch.setenv("CROWDTHREAT_ENABLED", "true")
|
||||
monkeypatch.setitem(_store.active_layers, "crowdthreat", True)
|
||||
monkeypatch.setattr(crowdthreat, "fetch_with_curl", lambda *args, **kwargs: _CrowdThreatResponse())
|
||||
|
||||
crowdthreat.fetch_crowdthreat()
|
||||
|
||||
assert _store.latest_data["crowdthreat"][0]["id"] == "ct-1"
|
||||
assert _store.latest_data["crowdthreat"][0]["source"] == "CrowdThreat"
|
||||
Reference in New Issue
Block a user