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

FleetOps — Work Done (2026-07-02)

Execution log for 260702_fix_plan.md Phase A. Local changes only — nothing committed, pushed, or deployed.

Changes

Finding Files What changed
FO-SEC-01 src/index.html escapeHtml moved to HELPERS with a new esc() shorthand; all previously-unescaped API strings now escape: renderVehicles (vehicle_number, cost_centre, assigned_city), renderDrivers (driver_name, assigned_city), renderFuel banner notes, renderFuelVehicles, renderFuelDepartments, renderFuelRecent (plate, department, driver, fuel_type), and the three catch-block error banners (e.message). WhatsApp-sourced Fuel Log strings can no longer inject markup.
FO-SEC-02 src/index.html SRI integrity + crossorigin="anonymous" on Chart.js 4.4.1, maplibre-gl 4.7.1 JS and CSS (sha384 computed from the exact CDN payloads; recompute one-liner documented inline for future bumps).
FO-SEC-03 Caddyfile Security header block: nosniff, Referrer-Policy: strict-origin-when-cross-origin, CSP (self + pinned CDNs + CARTO basemap + the two fleet APIs; worker-src blob: for MapLibre; frame-ancestors 'none'), -Server. script-src retains 'unsafe-inline' because the app is a single inline script block.
FO-BUG-01 src/index.html loadLive() now throws on non-OK responses instead of parsing error bodies.
FO-BUG-02 src/index.html The 15 s live-position poll skips while document.hidden or when the active tab isn't Tickets; switching back to Tickets or re-focusing the page triggers an immediate refresh. Cuts thousands of pointless API calls/day per idle viewer.
FO-OPS-01 src/index.html Missing-API_BASE fallback now targets the prod bridge (fleetapi.rahamafresh.com) per the documented design (was the staging bridge).

Verification

  • Inline app script extracted and passed node --check (no syntax errors).
  • Modified Caddyfile passed caddy validate inside the deployed fleetops image on twala ("Valid configuration").
  • 37 esc(/escapeHtml( call sites after the change (was 21, tickets-only).

NOT done — operational

  1. Deploy flows through the Forgejo → Coolify webhook on push (staging first). Before prod promotion, redeploy the prod dashboard_api bridge — it lacks the INC/CRQ/fuel-fills routes this SPA version calls.
  2. After the staging deploy, click through all four tabs with DevTools open to confirm zero CSP violations before promoting.
  3. Root scratch files (marker-preview.html, tracksolid_db_connection.md, webook_instructions.txt) left untouched — decide keep/commit/delete; tracksolid_db_connection.md should be rewritten for the SSH-tunnel workflow once the public DB port closes.