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.7 KiB
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 validateinside the deployed fleetops image on twala ("Valid configuration"). - 37
esc(/escapeHtml(call sites after the change (was 21, tickets-only).
NOT done — operational
- Deploy flows through the Forgejo → Coolify webhook on push (staging first).
Before prod promotion, redeploy the prod
dashboard_apibridge — it lacks the INC/CRQ/fuel-fills routes this SPA version calls. - After the staging deploy, click through all four tabs with DevTools open to confirm zero CSP violations before promoting.
- Root scratch files (
marker-preview.html,tracksolid_db_connection.md,webook_instructions.txt) left untouched — decide keep/commit/delete;tracksolid_db_connection.mdshould be rewritten for the SSH-tunnel workflow once the public DB port closes.