SIGINT
May 14, 2026 · View on GitHub
Real-time OSINT dashboard with live aircraft, vessel, seismic, fire, weather, and event tracking on an interactive globe. Built with Bun, React 19, and a custom Canvas 2D + Web Worker rendering engine. Installable as a PWA.
Screenshot

Table of Contents
Features
Live Data
- Aircraft tracking (adsb.fi)
- AIS vessel tracking (aisstream.io)
- Seismic monitoring (USGS)
- Fire hotspot detection (NASA FIRMS)
- Severe weather alerts (NOAA)
- Tropical cyclone tracking (NHC — active storms, 5-day forecast cone, advisories)
- GDELT event intelligence
- RSS news aggregation (6 world sources)
- HLS video feeds (iptv-org)
Tropical Cyclone Tracking
Active Atlantic, Eastern Pacific, and Central Pacific basins from the NHC CurrentStorms.json feed (server-proxied every 30 min). For each active storm:
- Current position, max wind, classification, basin
- Official NHC 5-day forecast cone (KMZ parsed server-side into a GeoJSON polygon)
- Forecast track points (12h–120h)
- Text products: Public Advisory, Forecast Discussion, Wind Probabilities
- Storm dossier pane with the full advisory text and forecast table
- Correlation rules: Hurricane Hunter aircraft proximity, ships sheltering in the lee, GDELT events on the forecast track
Out-of-season returns an empty activeStorms: [] as a 200, not a 503. Hemisphere-aware: the Atlantic basin gates are skipped between December and May unless the cache already has a non-empty snapshot.
Intelligence
- Correlation engine with cross-source products and scored alerts
- Military aircraft classification
- Watch mode (automated globe tour)
- Entity dossier with photos, routes, metadata
Visualization
- Globe and flat map projections
- Multi-pane resizable layout with drag, minimize, presets
- Camera lock-on, isolation modes, trail rendering
- Global search with live globe filtering
- Virtual-scrolling data table
- Live ticker feed
Platform
- Dark/light themes
- Mobile responsive with separate layout presets
- PWA with offline support, update notifications, pull-to-refresh
- Offline indicator with connectivity detection
- Cookie-authenticated API (HMAC-SHA256, HttpOnly)
Installation
git clone https://github.com/iitoneloc/sigint.git
cd sigint
bun install
Create a .env file in the project root with at minimum:
SIGINT_SERVER_SECRET=<output of openssl rand -hex 32>
Optionally add keys for ship and fire data:
AISSTREAM_API_KEY=<your aisstream.io key>
FIRMS_MAP_KEY=<your NASA FIRMS key>
Quick Start
See Deployment for dev, production, and Heroku options.
Environment Variables
| Variable | Required | Description |
|---|---|---|
SIGINT_SERVER_SECRET | Yes | Auth token signing key. Must be ≥32 chars. openssl rand -hex 32. Server exits 78 without it. |
AISSTREAM_API_KEY | No | aisstream.io key for live ship data |
FIRMS_MAP_KEY | No | NASA FIRMS key for fire data |
DOMAIN | No | Domain for Let's Encrypt TLS |
PORT | No | Server port (default: 5500) |
SIGINT_RATE_LIMIT_PER_MINUTE | No | Per-client rate-limit cap (default 60). Sliding-window limiter applied to every route. |
SIGINT_TRUSTED_PROXY_HOPS | No | Number of trusted proxies in front of the app (default 0). Drives X-Forwarded-For rightmost-N client IP extraction. |
Data Sources
| Layer | Source | Poll |
|---|---|---|
| Aircraft | adsb.fi (server-side tile sweep, 108 tiles × 250 nm, cold-start hubs) | 240s |
| Ships | aisstream.io (server WebSocket) | 300s |
| Seismic | USGS (client-side) | 420s |
| Fires | NASA FIRMS (server-side) | 600s |
| Weather | NOAA (client-side) | 300s |
| Cyclones | NHC (server-side; KMZ cone + advisory text products) | 30m |
| Events | GDELT 2.0 (server-side) | 15m |
| News | 6 RSS feeds (server-side) | 10m |
Aircraft data source — adsb.fi (replaces OpenSky)
Aircraft data is served by adsb.fi, a community-supported ADS-B aggregator. Earlier versions of this project used OpenSky Network as the upstream; OpenSky deprecated their free anonymous read tier, so the aircraft path migrated to adsb.fi end-to-end:
- The server runs a 108-tile sweep (250 nm radius each), 3 s spacing, every 300 s, against
https://opendata.adsb.fi/api/v3/lat/{lat}/lon/{lon}/dist/{nm}. The browser never hits adsb.fi directly — adsb.fi enforces a 1 req/sec/IP cap that a per-user budget would burn instantly. - Records are enriched against the read-only
ac-db.sqlite(~617k records) before they hit the cache. The SQLite is built from a one-time export of the OpenSky aircraft metadata database viascripts/convert-aircraft-csv.tsand is checked in as the bundled NDJSON source (src/server/data/ac-db.ndjson→ac-db.sqliteat build time). No live calls to OpenSky remain anywhere in the runtime. - The hex-prefix → country mapping in
src/server/data/icao24CountryRanges.tsis derived from ICAO Annex 10 and replaces the previous OpenSky country field.
Forks: if you're migrating from an older fork that still calls opensky-network.org directly, the change is server-only — the /api/aircraft/states route shape is unchanged and the client provider (features/tracking/aircraft/data/provider.ts) doesn't need updating.
Testing
bun test # run all tests
bun test --watch # watch mode
bun run docker:test # run in Docker
Deployment
Development
bun run docker:dev:up # https://localhost (self-signed cert)
bun run docker:dev:down # stop
Dev-only fixture overrides
Two env vars short-circuit live data fetches in development so you can
work against a known frozen state. Both are gated on
NODE_ENV !== "production" and ignored in production builds.
| Env var | Source it overrides | Valid labels |
|---|---|---|
CYCLONES_FIXTURE | /api/cyclones/latest (server fetches NHC) | single-cat3, single-cat5, multi-storm, subtropical-example, tropical-depression, empty-out-of-season |
AIRCRAFT_FIXTURE | /api/aircraft/states (server fetches adsb.fi tile sweep) | (none shipped — capture via bun run scripts/capture-fixture.ts aircraft <label>) |
Labels match /^[a-z0-9-]+$/ (OWASP A01 — strict allowlist before any
file lookup) and resolve to tests/fixtures/<source>/<label>.json.
Invalid labels throw at startup; missing files throw with the resolved
path. To use:
CYCLONES_FIXTURE=multi-storm bun run dev
AIRCRAFT_FIXTURE=conus-snapshot bun run dev
Or via Docker Compose (docker-compose.dev.yml passes both through):
CYCLONES_FIXTURE=single-cat5 bun run docker:dev:up
Production
bun run docker:prod:up # http://localhost:5500
bun run docker:prod:down # stop
Production with TLS
DOMAIN=sigint.example.com bun run docker:prod:tls:up
bun run docker:prod:tls:down # stop
Heroku
git push heroku main
Cleanup
bun run docker:clean:all # remove containers, volumes, images
PWA
SIGINT is installable as a Progressive Web App. After visiting the deployed app:
- Desktop (Chrome/Edge): Click the install icon in the address bar
- iOS Safari: Share > Add to Home Screen
- Android Chrome: Menu > Add to Home Screen
The service worker caches the app shell for offline boot. Live data loads from IndexedDB when offline. An offline indicator bar appears when connectivity is lost, with a RETRY button and pull-to-refresh on touch devices. When an update is available, a banner prompts the user to reload — no silent mid-session code swaps.
Documentation
Full technical docs in docs/ covering architecture, data flow, feature system, pane system, rendering, caching, search, and constraints.
License
Dual-licensed:
- Non-commercial free under the SIGINT Non-Commercial License
- Commercial contact the author for terms