mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-07-19 10:17:30 +02:00
71a9d9e144
PR #227 hardened most Wormhole/Infonet control surfaces behind require_local_operator and made the CrowdThreat fetcher opt-in. An audit of the codebase against that PR's stated goals turned up four classes of gap that the original change missed: 1. Two operator-only endpoints were left unprotected: - POST /api/wormhole/join: calls bootstrap_wormhole_identity() and flips the node into Tor mode, exactly the surface #227 hardened on /api/wormhole/identity/bootstrap. - POST /api/sigint/transmit: relays APRS-IS packets over radio using operator-supplied credentials. Anything that reached the API could transmit on the operator's authority. Both now require_local_operator. test_control_surface_auth.py extended with regression coverage for both. 2. Five third-party fetchers were still default-on, phoning home to politically/commercially sensitive upstreams on every poll cycle: - fimi.py -> euvsdisinfo.eu -> FIMI_ENABLED - prediction_markets -> Polymarket + Kalshi -> PREDICTION_MARKETS_ENABLED - financial.py -> Finnhub / yfinance -> FINANCIAL_ENABLED or FINNHUB_API_KEY - nuforc_enrichment -> huggingface.co -> NUFORC_ENABLED - news.py -> configured RSS feeds -> NEWS_ENABLED (default on, kill switch) Same CrowdThreat-style pattern: explicit env-var opt-in, empty the data slot and mark_fresh when disabled. New regression test file test_third_party_fetchers_opt_in.py asserts each fetcher's network entry point is not called when its gate is off. 3. The outbound User-Agent leaked both the operator's personal email and a fork-specific GitHub URL on every fetcher request. Consolidated to a single DEFAULT_USER_AGENT in network_utils.py, project-generic by default (no contact info), overridable via SHADOWBROKER_USER_AGENT for operators who want to identify themselves (e.g. for Nominatim or weather.gov usage-policy compliance). Six call sites updated; the Nominatim-specific override is preserved. 4. The same generic UA now also flows through the peer prekey lookup in mesh_wormhole_prekey.py, so DM first-contact requests no longer identify the caller as a Shadowbroker fork to the peer being queried. .env.example updated to document all new opt-in env vars. Tests: backend/tests/test_control_surface_auth.py (extended), backend/tests/test_crowdthreat_opt_in.py (unchanged, still passes), backend/tests/test_third_party_fetchers_opt_in.py (new, 7 tests). All 31 tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
107 lines
3.5 KiB
Python
107 lines
3.5 KiB
Python
"""Third-party fetchers that phone home to politically/commercially
|
|
sensitive upstreams must be operator opt-in only.
|
|
|
|
Companion to ``test_crowdthreat_opt_in.py`` — extends the same default-off
|
|
posture to:
|
|
|
|
* EUvsDisinfo FIMI (``FIMI_ENABLED``)
|
|
* Polymarket + Kalshi (``PREDICTION_MARKETS_ENABLED``)
|
|
* Finnhub / yfinance financial data (``FINANCIAL_ENABLED`` /
|
|
``FINNHUB_API_KEY``)
|
|
* NUFORC HuggingFace dataset (``NUFORC_ENABLED``)
|
|
|
|
Each test asserts that with the env var unset (or set to a falsy value)
|
|
the fetcher's network entry point is NOT called.
|
|
"""
|
|
|
|
|
|
def _explode(*_args, **_kwargs):
|
|
raise AssertionError("upstream called while fetcher was meant to be disabled")
|
|
|
|
|
|
def test_fimi_disabled_by_default_does_not_call_upstream(monkeypatch):
|
|
from services.fetchers import _store, fimi
|
|
|
|
monkeypatch.delenv("FIMI_ENABLED", raising=False)
|
|
monkeypatch.setitem(_store.latest_data, "fimi", [{"id": "old"}])
|
|
monkeypatch.setattr(fimi, "fetch_with_curl", _explode)
|
|
|
|
fimi.fetch_fimi()
|
|
|
|
assert _store.latest_data["fimi"] == []
|
|
|
|
|
|
def test_fimi_falsy_value_does_not_call_upstream(monkeypatch):
|
|
from services.fetchers import _store, fimi
|
|
|
|
monkeypatch.setenv("FIMI_ENABLED", "false")
|
|
monkeypatch.setitem(_store.latest_data, "fimi", [{"id": "old"}])
|
|
monkeypatch.setattr(fimi, "fetch_with_curl", _explode)
|
|
|
|
fimi.fetch_fimi()
|
|
|
|
assert _store.latest_data["fimi"] == []
|
|
|
|
|
|
def test_prediction_markets_disabled_by_default(monkeypatch):
|
|
from services.fetchers import _store, prediction_markets
|
|
|
|
monkeypatch.delenv("PREDICTION_MARKETS_ENABLED", raising=False)
|
|
monkeypatch.setitem(_store.latest_data, "prediction_markets", [{"id": "old"}])
|
|
monkeypatch.setattr(
|
|
prediction_markets, "fetch_prediction_markets_raw", _explode
|
|
)
|
|
|
|
prediction_markets.fetch_prediction_markets()
|
|
|
|
assert _store.latest_data["prediction_markets"] == []
|
|
|
|
|
|
def test_financial_disabled_when_no_optin_or_api_key(monkeypatch):
|
|
"""yfinance fallback path must not run silently — needs FINANCIAL_ENABLED."""
|
|
from services.fetchers import _store, financial
|
|
|
|
monkeypatch.delenv("FINANCIAL_ENABLED", raising=False)
|
|
monkeypatch.delenv("FINNHUB_API_KEY", raising=False)
|
|
monkeypatch.setitem(_store.latest_data, "financial", {"BTC": {"price": 1}})
|
|
monkeypatch.setattr(financial, "_fetch_finnhub_quote", _explode)
|
|
monkeypatch.setattr(financial, "_fetch_yfinance_single", _explode)
|
|
|
|
financial.fetch_financial_markets()
|
|
|
|
assert _store.latest_data["financial"] == {}
|
|
|
|
|
|
def test_financial_enabled_via_finnhub_api_key(monkeypatch):
|
|
"""Presence of FINNHUB_API_KEY counts as explicit opt-in."""
|
|
from services.fetchers import financial
|
|
|
|
monkeypatch.delenv("FINANCIAL_ENABLED", raising=False)
|
|
monkeypatch.setenv("FINNHUB_API_KEY", "test-key")
|
|
|
|
assert financial.financial_fetch_enabled() is True
|
|
|
|
|
|
def test_nuforc_disabled_by_default_skips_download(monkeypatch):
|
|
from services.fetchers import nuforc_enrichment
|
|
|
|
monkeypatch.delenv("NUFORC_ENABLED", raising=False)
|
|
monkeypatch.setattr(nuforc_enrichment, "fetch_with_curl", _explode)
|
|
|
|
result = nuforc_enrichment._download_and_build()
|
|
|
|
assert result is None
|
|
|
|
|
|
def test_news_default_on_but_killable(monkeypatch):
|
|
"""News defaults on (kill switch only), but NEWS_ENABLED=false must disable it."""
|
|
from services.fetchers import _store, news
|
|
|
|
monkeypatch.setenv("NEWS_ENABLED", "false")
|
|
monkeypatch.setitem(_store.latest_data, "news", [{"id": "old"}])
|
|
monkeypatch.setattr(news, "fetch_with_curl", _explode)
|
|
|
|
news.fetch_news()
|
|
|
|
assert _store.latest_data["news"] == []
|