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

27 lines
2.3 KiB
Markdown

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