docs: record SQL-injection re-audit (clean) + D25 PWA/mobile + nginx XFF

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 4 days ago
parent ce6b564e2b
commit 0a7b7e6118

@ -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 (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 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. 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.

Loading…
Cancel
Save