mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-27 17:42:29 +02:00
729ea78cb2
External report from @jmleclercq: AISStream's Let's Encrypt cert
expired on 2026-05-20 (verified — their renewal pipeline failed), so
the AIS WebSocket connection dies with CERT_HAS_EXPIRED and the
maritime layer empties out. The reporter worked around it locally by
passing { rejectUnauthorized: false } to the WebSocket constructor and
asked whether we should add an env var for that.
That fix is the wrong fix. Disabling TLS validation entirely lets any
network attacker MITM the WebSocket and inject fake ship positions —
same class as the GDELT plaintext-HTTP MITM we just closed in #199.
Adding an env var for it would be an attractive nuisance: operators
set it once during a bad cert week and then forget, leaving themselves
open to MITM forever.
Right fix: SPKI pinning, same pattern as the Tor bundle digest pinning
in #201. The insight is that Let's Encrypt renewals keep the SAME
public key by default, so the SPKI hash survives normal cert rotation.
We can relax the date check while keeping the identity check.
Mechanics:
backend/data/aisstream_spki_pins.json (new)
Pinned SHA-256 hashes of the DER-encoded SPKI bytes for
stream.aisstream.io. Captured 2026-05-20 from the live cert.
Format is base64(sha256(pubkey_der)), matching the canonical
openssl pipeline. Whitelisted in .gitignore alongside the other
static reference data files (KiwiSDR directory, Tor bundle
digests).
backend/ais_proxy.js
Path A (99.9% of the time): normal TLS validation. Untouched.
Path B (on CERT_HAS_EXPIRED only): re-handshake with
rejectUnauthorized=false JUST to read the leaf cert, compute its
SPKI hash, compare against the pinned list. If match → upstream
is still the genuine AISStream → re-open the WebSocket with
rejectUnauthorized=false and log DEGRADED MODE. If no match →
refuse the connection, log loudly: this would be a real MITM.
Pin file is looked up in three locations so the same code works
in the Docker backend, the Tauri desktop runtime, and any
operator-relocated layout (SHADOWBROKER_AIS_PINS env var).
Embedded fallback list inside the JS so portable installs that
haven't shipped the JSON still work.
backend/services/ais_stream.py
Captures the proxy's status markers from stdout
({"__ais_proxy_status": {"degraded_tls": true}}) into a module-
level snapshot. Exposes ais_proxy_status() for the health
endpoint. Doesn't touch the data plane — degraded mode keeps
receiving vessel data, just with weaker MITM protection.
backend/routers/health.py + backend/services/schemas.py
/api/health now includes an ais_proxy block with degraded_tls.
Top-level status escalates ok -> degraded when AIS is in
degraded TLS mode (but won't downgrade a worse SLO status).
Operators get a visible signal that they're in degraded mode
without needing to grep logs.
Tests: backend/tests/test_ais_spki_pinning.py (7 tests)
- Pin file structure validation (JSON, host entry, base64 SHA-256)
- ais_proxy_status() snapshot semantics (starts empty, defensive copy)
- /api/health surfaces ais_proxy.degraded_tls when set
- /api/health returns empty ais_proxy when proxy hasn't reported
Node.js syntax check passes (node --check) on both backend/ais_proxy.js
and the Tauri runtime mirror.
When AISStream renews their cert (likely within hours-to-days), the
normal-TLS path succeeds on next reconnect and degraded_tls clears
automatically. No operator action needed. If they instead rotate their
server key, the SPKI check will fail and we'll need to add the new
hash to backend/data/aisstream_spki_pins.json before removing the old
one.
Credit: @jmleclercq for the clear report and the careful workaround
verification (Node version, ws version, manual probe).
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
256 lines
5.2 KiB
Plaintext
256 lines
5.2 KiB
Plaintext
# shadowbroker .gitignore
|
|
# ----------------------
|
|
|
|
# Dependencies
|
|
node_modules/
|
|
venv/
|
|
env/
|
|
.venv/
|
|
backend/.venv-dir
|
|
backend/venv-repair*/
|
|
backend/.venv-repair*/
|
|
|
|
# Environment Variables & Secrets
|
|
.env
|
|
.envrc
|
|
.env.local
|
|
.env.development.local
|
|
.env.test.local
|
|
.env.production.local
|
|
.npmrc
|
|
.pypirc
|
|
.netrc
|
|
*.pem
|
|
*.key
|
|
*.crt
|
|
*.csr
|
|
*.p12
|
|
*.pfx
|
|
id_rsa
|
|
id_rsa.*
|
|
id_ed25519
|
|
id_ed25519.*
|
|
known_hosts
|
|
authorized_keys
|
|
|
|
# Python caches & compiled files
|
|
__pycache__/
|
|
*.py[cod]
|
|
*$py.class
|
|
*.so
|
|
.Python
|
|
.ruff_cache/
|
|
.pytest_cache/
|
|
.mypy_cache/
|
|
.hypothesis/
|
|
.tox/
|
|
|
|
# Next.js build output
|
|
.next/
|
|
out/
|
|
build/
|
|
*.tsbuildinfo
|
|
|
|
# Deprecated standalone Infonet Terminal skeleton (migrated into frontend/src/components/InfonetTerminal/)
|
|
frontend/infonet-terminal/
|
|
|
|
# Rust build artifacts (privacy-core)
|
|
target/
|
|
target-test/
|
|
|
|
# ========================
|
|
# LOCAL-ONLY: extra/ folder
|
|
# ========================
|
|
# All internal docs, planning files, raw data, backups, and dev scratch
|
|
# live here. NEVER commit this folder.
|
|
extra/
|
|
|
|
# ========================
|
|
# Application caches & runtime DBs (regenerate on startup)
|
|
# ========================
|
|
backend/ais_cache.json
|
|
backend/carrier_cache.json
|
|
backend/cctv.db
|
|
cctv.db
|
|
*.db
|
|
*.sqlite
|
|
*.sqlite3
|
|
|
|
# ========================
|
|
# backend/data/ — blanket ignore, whitelist static reference files
|
|
# ========================
|
|
# Everything in data/ is runtime-generated state (encrypted keys,
|
|
# MLS bindings, relay spools, caches) and MUST NOT be committed.
|
|
# Only static reference datasets that ship with the repo are whitelisted.
|
|
backend/data/*
|
|
!backend/data/datacenters.json
|
|
!backend/data/datacenters_geocoded.json
|
|
!backend/data/military_bases.json
|
|
!backend/data/plan_ccg_vessels.json
|
|
!backend/data/plane_alert_db.json
|
|
!backend/data/power_plants.json
|
|
!backend/data/tracked_names.json
|
|
!backend/data/yacht_alert_db.json
|
|
# Issue #206: bundled KiwiSDR receiver directory used as last-resort
|
|
# fallback when rx.linkfanel.net (HTTP-only upstream) is unreachable
|
|
# or returns content that fails our integrity validation.
|
|
!backend/data/kiwisdr_directory.json
|
|
# Issue #201: pinned SHA-256 digests for known Tor Expert Bundle URLs.
|
|
# Used as a second verification source when upstream .sha256sum fails.
|
|
!backend/data/tor_bundle_digests.json
|
|
# Issue #258: SPKI pins for stream.aisstream.io so we can survive upstream
|
|
# Let's Encrypt renewal failures without disabling TLS validation entirely.
|
|
!backend/data/aisstream_spki_pins.json
|
|
|
|
# OS generated files
|
|
.DS_Store
|
|
.DS_Store?
|
|
._*
|
|
.Spotlight-V100
|
|
.Trashes
|
|
ehthumbs.db
|
|
Thumbs.db
|
|
|
|
# IDEs and Editors
|
|
.vscode/
|
|
.idea/
|
|
*.suo
|
|
*.ntvs*
|
|
*.njsproj
|
|
*.sln
|
|
*.sw?
|
|
|
|
# Vercel / Deployment
|
|
.vercel
|
|
|
|
# ========================
|
|
# Temp / scratch / debug files
|
|
# ========================
|
|
tmp/
|
|
*.log
|
|
*.tmp
|
|
*.bak
|
|
*.swp
|
|
*.swo
|
|
out.txt
|
|
out_sys.txt
|
|
rss_output.txt
|
|
merged.txt
|
|
tmp_fast.json
|
|
diff.txt
|
|
local_diff.txt
|
|
map_diff.txt
|
|
TERMINAL
|
|
|
|
# Debug dumps & release artifacts
|
|
backend/dump.json
|
|
backend/debug_fast.json
|
|
backend/nyc_sample.json
|
|
backend/nyc_full.json
|
|
backend/liveua_test.html
|
|
backend/out_liveua.json
|
|
backend/out.json
|
|
backend/temp.json
|
|
backend/seattle_sample.json
|
|
backend/sgp_sample.json
|
|
backend/wsdot_sample.json
|
|
backend/xlsx_analysis.txt
|
|
frontend/server_logs*.txt
|
|
frontend/cctv.db
|
|
frontend/eslint-report.json
|
|
*.zip
|
|
*.tar.gz
|
|
*.xlsx
|
|
|
|
# Old backups & repo clones
|
|
.git_backup/
|
|
local-artifacts/
|
|
release-secrets/
|
|
shadowbroker_repo/
|
|
frontend/src/components.bak/
|
|
frontend/src/components/map/icons/backups/
|
|
|
|
# Coverage
|
|
coverage/
|
|
.coverage
|
|
.coverage.*
|
|
dist/
|
|
|
|
# Test scratch files (not in tests/ folder)
|
|
backend/test_*.py
|
|
backend/services/test_*.py
|
|
|
|
# Local analysis & dev tools
|
|
backend/analyze_xlsx.py
|
|
backend/services/ais_cache.json
|
|
graphify/
|
|
graphify-out/
|
|
|
|
# ========================
|
|
# Internal docs & brainstorming (never commit)
|
|
# ========================
|
|
docs/*
|
|
!docs/mesh/
|
|
docs/mesh/*
|
|
!docs/mesh/threat-model.md
|
|
!docs/mesh/claims-reconciliation.md
|
|
!docs/mesh/mesh-canonical-fixtures.json
|
|
!docs/mesh/mesh-merkle-fixtures.json
|
|
!docs/mesh/wormhole-dm-root-operations-runbook.md
|
|
.local-docs/
|
|
infonet-economy/
|
|
updatestuff.md
|
|
ROADMAP.md
|
|
UPDATEPROTOCOL.md
|
|
CLAUDE.md
|
|
DOCKER_SECRETS.md
|
|
|
|
# Misc dev artifacts
|
|
clean_zip.py
|
|
zip_repo.py
|
|
refactor_cesium.py
|
|
jobs.json
|
|
|
|
# Claude / AI
|
|
.claude
|
|
.mise.local.toml
|
|
.codex-tmp/
|
|
prototype/
|
|
.runtime/
|
|
|
|
# ========================
|
|
# Runtime state & operator-local data (never commit)
|
|
# ========================
|
|
# TimeMachine snapshot cache — regenerated at runtime, can be 100 MB+
|
|
backend/timemachine/
|
|
# Operator witness keys, identity material, transparency ledgers (machine-local)
|
|
ops/
|
|
# Runtime DM relay state
|
|
dm_relay.json
|
|
# Dev scratch notes
|
|
improvements.txt
|
|
|
|
# ========================
|
|
# Custody verification temp dirs (runtime test artifacts with private keys!)
|
|
# ========================
|
|
backend/sb-custody-verify-*/
|
|
|
|
# Python egg-info (build artifact, regenerated by pip install -e)
|
|
*.egg-info/
|
|
|
|
# Privacy-core debug build (Windows DLL, 3.6 MB, not shipped)
|
|
privacy-core/debug/
|
|
|
|
# Desktop-shell export stash dirs (empty temp dirs from Tauri build)
|
|
frontend/.desktop-export-stash-*/
|
|
|
|
# Wormhole logs (can be 30 MB+ each, runtime-generated)
|
|
backend/data/wormhole_stderr.log
|
|
backend/data/wormhole_stdout.log
|
|
|
|
# Runtime caches that already slip through the backend/data/* blanket
|
|
# (these are caught by the wildcard but listing for clarity)
|
|
|
|
# Compressed snapshot archives (can be 100 MB+)
|
|
*.json.gz
|