Skip to content

fix(security): give Aetherscope an explicit auth boundary - #15

Merged
clorth0 merged 1 commit into
mainfrom
refactor/aetherscope-auth-boundary
Oct 18, 2026
Merged

clorth0 merged 1 commit into
mainfrom
refactor/aetherscope-auth-boundary

Conversation

@clorth0

@clorth0 clorth0 commented Oct 18, 2026

Copy link
Copy Markdown
Owner

⚠️ User-visible change (only one)

When AETHERSCOPE_TOKEN is set (now required for the Docker/compose setup, which binds 0.0.0.0 inside the container), the browser shows a one-time token gate page instead of the app; entering the token sets an HttpOnly cookie and loads the app as before. Loopback with no token (the normal local run) is completely unchanged — no gate, no login.

Inventory (security question #2)

HTTP: GET / (shell), GET /captures/<path> (IQ download), GET /api/captures (list) — all unauthenticated, trusting the bind.
Socket handlers (29, characterized in tests/test_auth.py): connect plus start_sweep/decode/capture/adsb/radio/scan_radio/replay/scan, play_capture, decode_capture, cancel_capture, stop, snap_radio, refresh_device, list_captures, delete_capture, update_capture, list_bookmarks, add/update/delete/bump_bookmark, set_setting, redact_capture, start/stop_audio_record, list_inventory, clear_inventory — every one trusted the connection.
Bind: AETHERSCOPE_HOST default 127.0.0.1 (app.py), Dockerfile 0.0.0.0 relying on compose 127.0.0.1:8765:8765, allow_unsafe_werkzeug=True at the old app.py:1226. Frontend: same-origin io(), no token existed.

Design (pocock-codebase-design vocabulary)

  • Module backend/auth.py (named in docs/architecture.md): one AuthPolicy(host, token) — a deep module with a small interface (requires_auth, ensure_safe_startup(), check_token(), cookie make/verify, one is_authenticated(headers, cookies, auth) verdict). No new deps: stdlib hmac/hashlib, constant-time compare.
  • Seams: exactly two enforcement seams over that interface — Flask before_request for HTTP (capture routes included) and the Socket.IO connect handler that every socket handler sits behind (not per-handler copies).
  • Fail closed: non-loopback bind without a token refuses at startup with a clear error, before any socket is bound (verified live). allow_unsafe_werkzeug=True stays (Flask-SocketIO threading serves via Werkzeug); the startup guard is what makes reaching that line safe — non-loopback cannot get there without a token.
  • When auth is required (token set OR non-loopback): gate page → POST /auth/token → HttpOnly, SameSite=Strict, signed cookie (<issued>.<hmac>, key = token; raw token never in the cookie, rotating the token invalidates cookies). Socket auth reads that cookie, an auth={"token": ...} connect payload, or Authorization: Bearer / X-Aetherscope-Token headers (API clients). Frontend: io({withCredentials: true}); on manager error it probes /api/captures and bounces to / (the gate) only on 401.
  • Parity: loopback + no token = today's behaviour exactly, covered by a dedicated test and the untouched existing suites.
  • Dockerfile/compose/docs now honestly state the token requirement for the container bind (compose publishes 127.0.0.1:8765:8765 unchanged).

Scope: #2 only. Geotag defaults and capture-path exposure (#4/#5) untouched, except that HTTP auth now covers the capture routes as designed.

Tests + mutant

  • tests/test_auth.py (12, failing-first): startup refusal for non-loopback/no-token; handler-set characterization (a new socket handler not added to the list fails); real Flask + Socket.IO test-client cycles — 401 without/with-wrong token, 200 with Bearer/cookie, gate→cookie→shell+captures, capture download bytes, socket connect rejected/accepted, cookie-shared socket connect; loopback parity.
  • Mutant: inverting check_token's compare_digest → 6/12 red; restored → 12/12.
  • Local CI-way: all tests/test_*.py green, check_html_templates 0, check_architecture_names green, node --check all frontend JS green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant