Close tg12 outbound audit (#348-#366): operator UA, opt-ins, docs

- User-Agent is per-install handle only (no Shadowbroker product token)
- LiveUAMap: Windows UI consent when enabling Global Incidents; env override
- Meshtastic callsign upstream header off by default (opt-in true)
- Expanded docs/OUTBOUND_DATA.md and README link for CCTV, basemap, Broadcastify

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
BigBodyCobain
2026-06-03 15:01:32 -06:00
parent a3e5c98cd0
commit 363b5a49c8
19 changed files with 475 additions and 184 deletions
+95 -27
View File
@@ -1,43 +1,111 @@
# Outbound data and third-party exposure
Shadowbroker is **self-hosted**: each install uses its own backend egress IP (and optional `OPERATOR_HANDLE` in `User-Agent`). This documents intentional third-party contact for audit issues #348#366.
Shadowbroker is **self-hosted**: each install uses its own backend egress IP. This document is the operator-facing record for GitHub audit issues **#348#366** (tg12): what contacts third parties, why, and how to opt out without losing unrelated features.
## Architecture
| Path | Who calls third parties |
|------|-------------------------|
| UI → `/api/*` → fetchers | **Backend** |
| Map basemap tiles/fonts | **Browser** (CARTO, demotiles.maplibre.org) |
| CCTV proxy | **Backend** (with upstream-required `Referer` / `Origin`) |
| Map UI → `/api/*` → fetchers | **This installs backend** |
| Basemap tiles / fonts | **Operators browser** (CARTO, demotiles.maplibre.org) |
| CCTV still/video proxy | **Backend** (Referer/Origin set per agency — see #349) |
---
## Issue disposition summary
| Issue | Status | Approach |
|-------|--------|----------|
| **#351** | Fixed | Region dossier via backend proxy |
| **#352** | Fixed | Geocode via `/api/geocode` only |
| **#360** | Fixed | Wikipedia/Wikidata via backend |
| **#362** | Fixed | `DEEPSTATE_MIRROR_COMMIT` optional pin |
| **#363** | Fixed | Madrid KML HTTPS-first |
| **#364** | Fixed | KiwiSDR HTTPS-first + validation |
| **#348** | Accepted + gated | Windows UI opt-in; env override; stealth documented |
| **#349** | Accepted + documented | Agency-required Referer on backend proxy only |
| **#350** | Mitigated | Callsign in UA **off by default**; opt-in `MESHTASTIC_SEND_CALLSIGN_HEADER=true` |
| **#354** | Accepted + documented | Default basemap CDN; optional self-hosted tiles |
| **#361** | Mitigated | UA is **install handle only** (`operator-…`), not shared `Shadowbroker/` token |
| **#366** | Accepted + documented | Honest per-install scrape; feature degrades if blocked |
---
## Per-install User-Agent (#361)
- **Code:** `backend/services/network_utils.py``outbound_user_agent()`, `OPERATOR_HANDLE`
- **Sent:** `operator-7f3a92` or `your-handle (purpose: nominatim)`**no** shared app product name
- **Why:** Upstreams can rate-limit **one install**; a block on `operator-abc123` does not require blocking every Shadowbroker user
- **Override:** `SHADOWBROKER_USER_AGENT` replaces the entire string
- **Note:** The same handle across Wikipedia, Broadcastify, etc. still correlates **your** traffic across those sites — that is intentional per-install attribution, not anonymity
---
## LiveUAMap scraper (#348)
- **Layer:** `global_incidents` (LiveUAMap map pins; **GDELT** text still loads without LiveUAMap)
- **Code:** `backend/services/liveuamap_scraper.py` (Playwright + stealth for Turnstile)
- **Windows:** Scraper **off** until you enable **Global Incidents** and confirm the UI dialog → `backend/data/liveuamap_scraper_opt_in.json`
- **Linux/macOS:** Scraper runs when the layer is on (unless env forces off)
- **API:** `GET /api/liveuamap/scraper-status`, `POST /api/liveuamap/scraper-opt-in`
- **Env:** `SHADOWBROKER_ENABLE_LIVEUAMAP_SCRAPER=true|false` overrides UI on all platforms
- **Honesty:** Backend-only; no browser-direct LiveUAMap from end users. Stealth remains a functional tradeoff for Turnstile; disable layer or env if unacceptable
---
## CCTV proxy Referer / Origin (#349)
- **Code:** `backend/routers/cctv.py`, `backend/main.py`
- **Behavior:** Backend proxies streams and sets `Referer` / `Origin` each agency expects (e.g. `https://511ga.org/cctv`, `https://informo.madrid.es/`)
- **Exposure:** Agency sees **backend IP**, not each viewers browser
- **Not removed:** Without these headers, most public DOT/city feeds return 403 — this is not end-user browser impersonation, it is the same headers a normal browser session would send to play the feed
---
## Meshtastic map callsign (#350)
- **Layer:** `sigint_meshtastic` must be active for `fetch_meshtastic_nodes()`
- **Default:** `MESHTASTIC_SEND_CALLSIGN_HEADER=false` — callsign **not** sent to `meshtastic.liamcottle.net` unless you set `true`
- **Optional:** `MESHTASTIC_OPERATOR_CALLSIGN` for local display; header only when explicitly enabled
---
## Basemap CDN (#354)
- **Code:** `frontend/src/components/map/styles/mapStyles.ts`, `frontend/public/map-style.json`
- **Hosts:** `*.basemaps.cartocdn.com`, `demotiles.maplibre.org`
- **Exposure:** **Browser** loads tiles (client IP + pan/zoom), not the backend
- **Mitigation:** Self-host raster tiles and point MapLibre `sources` at your tile server (operator choice; not required for core features)
---
## Broadcastify top feeds (#366)
- **Code:** `backend/services/radio_intercept.py`
- **Behavior:** Backend fetches `https://www.broadcastify.com/listen/top` with per-install handle UA; parses public HTML for feed metadata and CDN stream URLs
- **Exposure:** Your backend IP; 5-minute cache
- **If blocked:** Panel shows empty list — feature not removed from the app
- **Not:** Fake Chrome UA or cloudscraper bypass (removed in Round 7a)
---
## Ukraine frontline mirror (#362)
- **Layer:** `ukraine_frontline` `frontlines` on the map (DeepStateMap polygons). **Not** UAP (`uap_sightings` / NUFORC).
- **Code:** `backend/services/geopolitics.py`
- **Default:** `cyterat/deepstate-map-data` @ `main`, latest `data/deepstatemap_data_*.geojson`
- **Pin:** `DEEPSTATE_MIRROR_COMMIT=<sha>` — immutable Git snapshot; bump SHA when you want newer lines
- **Optional:** `DEEPSTATE_MIRROR_REPO=owner/repo`
- **Layer:** `ukraine_frontline` / `frontlines`
- **Pin:** `DEEPSTATE_MIRROR_COMMIT`, optional `DEEPSTATE_MIRROR_REPO`
## Madrid CCTV (#363)
## Madrid CCTV (#363) / KiwiSDR (#364)
- **Ingest:** HTTPS-first KML on `datos.madrid.es` (catalog only); HTTP fallback if needed
- **Feeds:** Still images from URLs inside the KML (`informo.madrid.es`, etc.), proxied with `Referer: https://informo.madrid.es/` — unchanged by KML transport
- Madrid: HTTPS-first KML catalog; image URLs unchanged
- KiwiSDR: HTTPS-first directory fetch; shape validation + bundled fallback
## KiwiSDR (#364)
- HTTPS first, then HTTP; shape validation + bundled `backend/data/kiwisdr_directory.json`
## Other documented exposures
- **#354 Basemap:** browser → `*.basemaps.cartocdn.com`, `demotiles.maplibre.org`
- **#349 CCTV Referer:** required for many DOT/city streams; backend proxy only
- **#361 Operator UA:** `OPERATOR_HANDLE` / `outbound_user_agent()` per install
- **#366 Broadcastify:** backend scrape with honest UA
- **#348 LiveUAMap:** `SHADOWBROKER_ENABLE_LIVEUAMAP_SCRAPER` (default on Linux, off Windows)
---
## Operator checklist
1. Set `OPERATOR_HANDLE` if you want a recognizable contact on upstream logs.
2. Pin `DEEPSTATE_MIRROR_COMMIT` after reviewing a mirror commit (see `backend/.env.example`).
3. Set `SHADOWBROKER_ENABLE_LIVEUAMAP_SCRAPER=false` to disable LiveUAMap contact.
4. Self-host map tiles if basemap CDN exposure matters.
1. Set `OPERATOR_HANDLE` if you want a recognizable name on upstream logs.
2. Pin `DEEPSTATE_MIRROR_COMMIT` for reproducible frontlines (optional).
3. Windows: enable Global Incidents in UI only if you accept LiveUAMap server contact.
4. Set `SHADOWBROKER_ENABLE_LIVEUAMAP_SCRAPER=false` to forbid LiveUAMap entirely.
5. Set `MESHTASTIC_SEND_CALLSIGN_HEADER=true` only if you want callsign sent upstream.
6. Self-host map tiles if basemap CDN exposure matters.