fleetops/docs/260702_audit_report.md
david kiania a0022fbeaf fix(security): escape API strings, pin CDN scripts, add CSP (FO-SEC-01/02/03)
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>
2026-07-02 09:47:40 +03:00

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 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.