The tickets code escaped HTML, but the logistics + fuel renderers and the error banners interpolated API strings straight into innerHTML. Fuel Log fields (driver, department, fuel_type, plate) come from WhatsApp messages and vehicle/driver names from the Tracksolid registry — both user-controlled — so this was a stored-XSS path into every dispatcher's browser. - Hoist escapeHtml into HELPERS + add esc(); route every logistics/fuel renderer and the three error banners through it (21 -> 37 escaped call sites). - SRI integrity + crossorigin on Chart.js 4.4.1 and maplibre-gl 4.7.1 JS/CSS. - Caddyfile: CSP (self + pinned CDNs + CARTO basemap + the two fleet APIs), X-Content-Type-Options, Referrer-Policy, frame-ancestors 'none', -Server. Validated with `caddy validate` inside the deployed image. - loadLive(): check r.ok; pause the 15s live poll while hidden or off the Tickets tab, refresh immediately on return (FO-BUG-01/02). - Missing-API_BASE fallback flipped staging -> prod, matching the documented design (FO-OPS-01). Inline app script passes `node --check`. Audit + plan + work log in docs/260702_*. Local only; not deployed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3.5 KiB
FleetOps — Platform Audit Report (2026-07-02)
Part of the 2026-07-02 cross-repo audit (tracksolid + fleettickets + fleetops; see
the tracksolid repo's docs/reports/260702_platform_audit_report.md for the
DB/host-wide findings). Scope here: the SPA (src/index.html + src/env.js),
the Caddy/Docker serving layer, and the two deployed containers on twala.
Companion docs: 260702_fix_plan.md, 260702_work_done.md.
High
FO-SEC-01 — Stored XSS: API strings rendered into innerHTML unescaped
The tickets explorer and map popups escape correctly (escapeHtml), but the
logistics tables (renderVehicles, renderDrivers), the fuel banner notes, and
all three Fuel Log tables (renderFuelVehicles, renderFuelDepartments,
renderFuelRecent) interpolate API strings straight into innerHTML. The Fuel
Log fields (driver, department, fuel_type, plate) originate from WhatsApp
messages — genuinely user-generated content — and vehicle/driver names come from
the Tracksolid registry, editable by any fleet-console user. A crafted name like
<img src=x onerror=…> executes in every dispatcher's browser. Error banners also
render e.message unescaped (server-influenced text).
FO-SEC-02 — CDN scripts loaded without Subresource Integrity
Chart.js (jsdelivr) and MapLibre (unpkg) load with no integrity/crossorigin
attributes — a compromised or MITM'd CDN response executes arbitrary script with
access to the fleet APIs.
FO-SEC-03 — No security headers from Caddy
No Content-Security-Policy, X-Content-Type-Options, Referrer-Policy, or
frame-ancestors protection. Combined with FO-SEC-01 this leaves injected markup
free to load external script and exfiltrate anywhere.
Medium
FO-BUG-01 — loadLive() parses the response without checking r.ok
A 4xx/5xx from /webhook/live-positions throws inside r.json() on non-JSON
bodies with an unhelpful error; harmless but noisy.
FO-BUG-02 — Live-position poll never pauses
The 15-second /webhook/live-positions poll starts when the Tickets map is first
opened and then runs forever — including when the user is on the Logistics/Fuel
tabs and when the browser tab is hidden overnight. A dashboard left open ≈ 5,760
wasted API calls/day per viewer.
FO-OPS-01 — Fallback API base pointed at staging
index.html fell back to https://fleetapi.fivetitude.com when env.js isn't
templated. CLAUDE.md documents the intended fallback as the prod API. A prod
pod missing API_BASE would silently lean on the staging bridge (and fail CORS
confusingly).
Notes / by design
- FO-OPS-02 — the prod container (
API_BASE=fleetapi.rahamafresh.com) runs commit21bca24, ~19 commits behind HEAD — this is the documented frozen-prod staging-umbrella pattern, not drift. But note: promotion must pair the SPA deploy with the proddashboard_apibridge redeploy — prod's bridge currently lacks all/webhook/{inc,crq}-*and/analytics/fuel-fillsroutes the newer SPA calls (see the tracksolid audit, OPS-01). - FO-OPS-03 — untracked scratch files at the repo root
(
marker-preview.html,tracksolid_db_connection.md,webook_instructions.txt).tracksolid_db_connection.mddocuments the publictwala:5433superuser connection pattern that the tracksolid audit is eliminating — update or remove once the SSH-tunnel workflow lands. - The serving layer is otherwise good: Caddyfile validated at build time,
healthcheck endpoint, runtime
API_BASEinjection viatemplates,no-storeon the shell and env.js.