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