From 0584a1d71dcf5c90ab6e6c88d23dde1f1be6d656 Mon Sep 17 00:00:00 2001 From: Tom-Neverwinter <3943634+Tom-Neverwinter@users.noreply.github.com> Date: Thu, 10 Sep 2026 14:41:17 -0500 Subject: [PATCH] fix: sanitize proxy error payloads, loopback dev-cctv, drop doc drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four small, independently-verified issues from the audit backlog: - #262/#263: the celestrak, terrain-heights, adsbdb and launch-library proxies put raw `err.message` / raw upstream error bodies into HTTP responses, contradicting the sanitized-error property KNOWN-ISSUES.md documents. They now log the real error server-side (matching the GBFS and CCTV proxy convention) and return a generic message. - #265: scripts/dev-cctv.sh defaulted to HOST=0.0.0.0 while dev-fresh.sh and dev-secure.sh default to loopback. Now loopback, opt in with HOST=0.0.0.0. - #274: naturalEarthRegions.js's internal haversineKm took (lon, lat, lon, lat) while every other haversineKm in src/ takes (lat, lon, lat, lon). Same name, same signature shape, opposite order — a silent wrong-distance landmine. Reordered to match; the one call site was updated, so behavior is unchanged. - #270: removed CCTV_AUTO_CALIBRATE and CCTV_DRAPE_MESH from .env.example; no code anywhere reads them. Not done: deduplicating the ~10 haversine implementations into one shared utility. That is issue #188's scope and a far larger diff; removing the argument-order landmine is what makes the current state safe. Verified: npm test (2705 pass / 0 fail), npm run build green. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01C3eGYgDYBo2BCUeDGwGvrp --- .env.example | 2 -- CHANGELOG.md | 10 ++++++++++ scripts/dev-cctv.sh | 3 ++- src/data/naturalEarthRegions.js | 5 +++-- vite.config.js | 13 +++++++++---- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 2a5479514..63cc90bf8 100644 --- a/.env.example +++ b/.env.example @@ -138,5 +138,3 @@ AISSTREAM_API_KEY= # CCTV_MAX_SOURCES=48 # overall cap on registered cameras # CCTV_PREFER_AUSTIN=1 # prioritize Austin cameras in the registry # CCTV_FORCE_AUSTIN= # force Austin-only sources -# CCTV_AUTO_CALIBRATE=1 # auto-calibrate camera projection (client flag) -# CCTV_DRAPE_MESH=1 # drape frames onto 3D geometry (client flag) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21544fa46..63cb46041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md ### Fixed +- `scripts/dev-cctv.sh` now binds to `localhost` by default, matching + `dev-fresh.sh` and `dev-secure.sh`. Set `HOST=0.0.0.0` to opt in to LAN + exposure. (#265) +- The Celestrak, terrain-heights, adsbdb, and launch-library proxies no longer + place raw exception text or upstream error bodies in HTTP responses; the real + error is logged server-side and the client gets a generic message. (#262, #263) +- `naturalEarthRegions.js`’s internal `haversineKm` takes `(lat, lon, lat, lon)` + like every sibling implementation instead of longitude-first. (#274) +- Dropped `CCTV_AUTO_CALIBRATE` and `CCTV_DRAPE_MESH` from `.env.example`; no + code reads them. (#270) - Mapped-site outages show their scheduled retry countdown and distinguish known Overpass rate limits, timeouts, and query failures. Search feedback no longer claims a refresh succeeded while the layer is unavailable or loading. diff --git a/scripts/dev-cctv.sh b/scripts/dev-cctv.sh index b69d84a14..339657af8 100755 --- a/scripts/dev-cctv.sh +++ b/scripts/dev-cctv.sh @@ -5,7 +5,8 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT_DIR" PORT="${PORT:-4173}" -HOST="${HOST:-0.0.0.0}" +# Local-only by default; set HOST=0.0.0.0 explicitly to expose on the LAN. +HOST="${HOST:-localhost}" CCTV_SOURCES_FILE="${CCTV_SOURCES_FILE:-config/cctv_sources.austin.json}" CCTV_PREFER_AUSTIN="${CCTV_PREFER_AUSTIN:-1}" CCTV_AUSTIN_MAX_SOURCES="${CCTV_AUSTIN_MAX_SOURCES:-36}" diff --git a/src/data/naturalEarthRegions.js b/src/data/naturalEarthRegions.js index 5929a8921..447cb1ba4 100644 --- a/src/data/naturalEarthRegions.js +++ b/src/data/naturalEarthRegions.js @@ -34,7 +34,8 @@ function ringAreaKm2(ring) { return Math.abs((sum * EARTH_RADIUS_KM * EARTH_RADIUS_KM) / 2); } -function haversineKm(lon1, lat1, lon2, lat2) { +/** Great-circle distance in km. Arg order matches every sibling: (lat, lon, lat, lon). */ +function haversineKm(lat1, lon1, lat2, lon2) { const dLat = toRad(lat2 - lat1); const dLon = toRad(lon2 - lon1); const h = Math.sin(dLat / 2) ** 2 @@ -147,7 +148,7 @@ function buildEntries(pack, kind) { polygons, areaKm2, bbox: [minLon, minLat, maxLon, maxLat], - bboxDiagonalKm: haversineKm(minLon, minLat, maxLon, maxLat), + bboxDiagonalKm: haversineKm(minLat, minLon, maxLat, maxLon), }); } return out; diff --git a/vite.config.js b/vite.config.js index 34dac5291..9b283704a 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1645,7 +1645,8 @@ function celestrakProxy() { send(502, 'celestrak fetch failed and no cache available', 'NONE'); } } catch (err) { - send(500, `celestrak proxy error: ${err?.message || err}`, 'ERROR'); + console.error('[celestrak-proxy]', err?.message || String(err)); + send(500, 'celestrak proxy error', 'ERROR'); } }); }, @@ -1755,10 +1756,12 @@ function rocketLaunchesProxy() { send(res, 200, stale.body, 'STALE-ERROR'); return; } + // Upstream error bodies stay server-side; the client gets a generic message. + if (error?.upstreamBody) console.warn('[launch-library-proxy] upstream error body:', error.upstreamBody); send( res, Number.isInteger(error?.upstreamStatus) ? error.upstreamStatus : 502, - error?.upstreamBody || JSON.stringify({ error: 'Launch Library 2 unavailable' }), + JSON.stringify({ error: 'Launch Library 2 unavailable' }), 'NONE', ); } @@ -2373,7 +2376,8 @@ function terrainHeightsProxy() { } send(outcome.status, outcome.body); } catch (err) { - send(500, { error: `terrain heights proxy error: ${err?.message || err}` }); + console.error('[terrain-heights-proxy]', err?.message || String(err)); + send(500, { error: 'terrain heights proxy error' }); } }); }, @@ -2494,7 +2498,8 @@ function adsbdbProxy() { } return send(404, { error: 'unknown endpoint' }); } catch (err) { - return send(500, { error: String(err?.message || err) }); + console.error('[adsbdb-proxy]', err?.message || String(err)); + return send(500, { error: 'adsbdb proxy error' }); } }); },