diff --git a/docs/06-DECISIONS.md b/docs/06-DECISIONS.md index 274e3b3..5ce47a6 100644 --- a/docs/06-DECISIONS.md +++ b/docs/06-DECISIONS.md @@ -420,3 +420,29 @@ the load-bearing mediums were fixed in seven batches, each committed green with (large shared-package refactor; the event-loop-DoS vector is mitigated by the login rate limiter now); rendered-PDF caching on `/share`; api.ts file split; broad naming-consistency renames (`staff`/`user`/`employee`, `at_wall`, schema.sql drift) — too risky pre-go-live. + +## SQL-injection re-audit ✅ CLEAN (2026-07-18) +The SQL-injection surface (transiently blocked by a safety classifier during the D24 run) +was re-run as a focused 4-angle workflow (string-interpolation, dynamic identifiers, the +`?`→`$n` rewriter, the CSV importer) with adversarial verification. **Result: 0 findings.** +The auditors confirmed clean, angle by angle: every UPDATE `SET` clause is built from a fixed +source-literal column allowlist with `?`-bound values; every WHERE fragment is a constant +with bound args; client/ticket search uses `LOWER(col) LIKE LOWER(?)` with the pattern bound; +all `LIMIT/OFFSET` are integer-clamped bound params and every `ORDER BY` is a static literal; +D21 field-spec keys are validated by `KEY_RE` and used only as JSON map keys inside a bound +`field_values`/`secrets_enc` blob (never as SQL identifiers); the CSV importer binds every +cell value and no CSV header becomes a column name; and `db-pg.ts` `toDollarParams` fails +closed (a mis-split breaks the query, never injects) since bound values never pass through it. + +## D25 — Installable PWA + phone compatibility ✅ (2026-07-18) +Founder ask: the console should be a PWA and phone-usable. Delivered without a heavy build +plugin: a hand-written `manifest.webmanifest` (standalone, teal theme, scope `/`) + an icon +set generated from the HQ mark (`pwa-192/512`, full-bleed `maskable-512`, `apple-touch-icon`, +`icon.svg`; `sharp` is a build-only devDep); a conservative service worker (`public/sw.js`) +that makes the app installable and fast on repeat loads but **never** intercepts `/api` or +`/share` (always live), does network-first navigations with an app-shell fallback, and +cache-first for hashed assets — registered from `main.tsx` in production builds only. Phone +polish on top of the existing `<900px` drawer + scrolling tables: safe-area insets +(`viewport-fit=cover`), 16px inputs (kills iOS focus-zoom), 40px tap targets, bottom-sheet +dialogs. Also hardened the deploy: `nginx` now sends `X-Forwarded-For $remote_addr` (replace, +not append) so the D24 per-IP rate limiters can't be bypassed by a spoofed header.