mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-28 01:52:28 +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>
68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
from fastapi import APIRouter, Request, Query, Depends
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import BaseModel
|
|
from limiter import limiter
|
|
from auth import require_admin, require_local_operator
|
|
from services.data_fetcher import get_latest_data
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/api/oracle/region-intel")
|
|
@limiter.limit("30/minute")
|
|
async def oracle_region_intel(
|
|
request: Request,
|
|
lat: float = Query(..., ge=-90, le=90),
|
|
lng: float = Query(..., ge=-180, le=180),
|
|
):
|
|
"""Get oracle intelligence summary for a geographic region."""
|
|
from services.oracle_service import get_region_oracle_intel
|
|
news_items = get_latest_data().get("news", [])
|
|
return get_region_oracle_intel(lat, lng, news_items)
|
|
|
|
|
|
@router.get("/api/thermal/verify")
|
|
@limiter.limit("10/minute")
|
|
async def thermal_verify(
|
|
request: Request,
|
|
lat: float = Query(..., ge=-90, le=90),
|
|
lng: float = Query(..., ge=-180, le=180),
|
|
radius_km: float = Query(10, ge=1, le=100),
|
|
):
|
|
"""On-demand thermal anomaly verification using Sentinel-2 SWIR bands."""
|
|
from services.thermal_sentinel import search_thermal_anomaly
|
|
result = search_thermal_anomaly(lat, lng, radius_km)
|
|
return result
|
|
|
|
|
|
@router.post("/api/sigint/transmit", dependencies=[Depends(require_local_operator)])
|
|
@limiter.limit("5/minute")
|
|
async def sigint_transmit(request: Request):
|
|
"""Send an APRS-IS message to a specific callsign. Requires ham radio credentials."""
|
|
from services.wormhole_supervisor import get_transport_tier
|
|
tier = get_transport_tier()
|
|
if str(tier or "").startswith("private_"):
|
|
return {"ok": False, "detail": "APRS transmit blocked in private transport mode"}
|
|
body = await request.json()
|
|
callsign = body.get("callsign", "")
|
|
passcode = body.get("passcode", "")
|
|
target = body.get("target", "")
|
|
message = body.get("message", "")
|
|
if not all([callsign, passcode, target, message]):
|
|
return {"ok": False, "detail": "Missing required fields: callsign, passcode, target, message"}
|
|
from services.sigint_bridge import send_aprs_message
|
|
return send_aprs_message(callsign, passcode, target, message)
|
|
|
|
|
|
@router.get("/api/sigint/nearest-sdr")
|
|
@limiter.limit("30/minute")
|
|
async def nearest_sdr(
|
|
request: Request,
|
|
lat: float = Query(..., ge=-90, le=90),
|
|
lng: float = Query(..., ge=-180, le=180),
|
|
):
|
|
"""Find the nearest KiwiSDR receivers to a given coordinate."""
|
|
from services.sigint_bridge import find_nearest_kiwisdr
|
|
kiwisdr_data = get_latest_data().get("kiwisdr", [])
|
|
return find_nearest_kiwisdr(lat, lng, kiwisdr_data)
|