fleetops/docs/260702_fix_plan.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

2.3 KiB

FleetOps — Fix Plan (2026-07-02)

Companion to 260702_audit_report.md and 260702_work_done.md.

Phase A — repo changes (implemented in this session)

Step Finding Change
A1 FO-SEC-01 Hoist escapeHtml into the HELPERS section, add an esc() convenience (escapes or em-dashes), and route every API string in the logistics + fuel renderers and the three error banners through it. The tickets code already escaped — now the whole file is consistent.
A2 FO-SEC-02 Add integrity="sha384-…" crossorigin="anonymous" to the three CDN assets (hashes computed from the exact pinned files; recompute command documented inline).
A3 FO-SEC-03 Caddyfile: X-Content-Type-Options: nosniff, Referrer-Policy, frame-ancestors 'none', and a CSP restricting script/style/img/font/connect origins to self + the two pinned CDNs + CARTO + the two fleet APIs. script-src keeps 'unsafe-inline' (the app is one inline script block); the CSP's value here is limiting external script origins and exfil targets.
A4 FO-BUG-01/02 loadLive() checks r.ok, and skips while document.hidden or when the Tickets tab isn't active; switching back to Tickets (or the page becoming visible) triggers an immediate refresh so markers don't wait out the 15 s interval.
A5 FO-OPS-01 Fallback API base flipped to the prod bridge (fleetapi.rahamafresh.com), matching the documented design.

Phase B — operational (needs operator decision)

# Item
B1 Promotion pairing: when this branch is promoted to prod (Coolify deploy of fleetops.rahamafresh.com), redeploy the prod dashboard_api bridge first — it currently lacks the /webhook/{inc,crq}-* + /analytics/fuel-fills routes the SPA calls.
B2 Clean up untracked root scratch files; rewrite tracksolid_db_connection.md for the SSH-tunnel workflow once the DB port closes.
B3 Longer-term: consider vendoring Chart.js/MapLibre into src/ (removes the CDN trust surface entirely and lets the CSP drop jsdelivr/unpkg).

Verification

  • Extracted inline script passes node --check.
  • Caddyfile passes caddy validate (run inside the deployed fleetops image).
  • Manual: staging deploy → open all four tabs, confirm charts/map/popups render, check DevTools console for CSP violations before promoting.