# 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 `` 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 commit `21bca24`, ~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 prod `dashboard_api` bridge redeploy** — prod's bridge currently lacks all `/webhook/{inc,crq}-*` and `/analytics/fuel-fills` routes 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.md` documents the public `twala:5433` superuser 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_BASE` injection via `templates`, `no-store` on the shell and env.js.