docs(security): SEC-A5 — /bills/offline commits bills with no authentication

Automated review (CRITICAL): route registered above requireAuth, identity
fields trusted from the body. Two fix options specified; must land before
any pilot. Companion to SEC-C6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 2 weeks ago
parent 1523ebbcce
commit ea0bc146bb

@ -259,6 +259,9 @@ Dealers legitimately see many tenants (their installs) and Enterprise HO sees ma
**SEC-A4 · Sync API accepts forged or mis-scoped ops** *(design Phase 0; enforced Phase 1)* **SEC-A4 · Sync API accepts forged or mis-scoped ops** *(design Phase 0; enforced Phase 1)*
The outbox means the cloud ingests client-generated events, and anything on a shop PC is attacker-controllable — a tampered POS could push ops with someone else's `tenant_id`, backdated bills, or fabricated stock movements (fake ITC). **Impact:** high — cross-tenant *write* corruption is worse than a read leak. **Mitigation:** **device identity, not just user identity** — every counter enrolls once (owner-approved), receives a device keypair, and signs its ops; the server **derives** `tenant_id`/`store_id`/`counter_id` from the device record and **rejects any op claiming otherwise** (client scope fields checked, never trusted); idempotency keys double as replay protection; ingest sanity gates require the invoice series to match the device's assigned counter series and flag documents dated outside ±72 h of server time as `clock_suspect` (SEC-D2). This is the same device lease/epoch primitive that fences cloned machines (SY-9) and enforces licensing (SEC-D). The outbox means the cloud ingests client-generated events, and anything on a shop PC is attacker-controllable — a tampered POS could push ops with someone else's `tenant_id`, backdated bills, or fabricated stock movements (fake ITC). **Impact:** high — cross-tenant *write* corruption is worse than a read leak. **Mitigation:** **device identity, not just user identity** — every counter enrolls once (owner-approved), receives a device keypair, and signs its ops; the server **derives** `tenant_id`/`store_id`/`counter_id` from the device record and **rejects any op claiming otherwise** (client scope fields checked, never trusted); idempotency keys double as replay protection; ingest sanity gates require the invoice series to match the device's assigned counter series and flag documents dated outside ±72 h of server time as `clock_suspect` (SEC-D2). This is the same device lease/epoch primitive that fences cloned machines (SY-9) and enforces licensing (SEC-D).
**SEC-A5 · Offline bill drain is unauthenticated (found by automated review, 2026-07-10 — rated CRITICAL)** *(fix in Phase 1 before any pilot)*
`POST /api/bills/offline` is registered *above* `r.use(requireAuth)` in `apps/store-server/src/api.ts`, and `commitBill` trusts `storeId`/`cashierId`/`shiftId` from the body — any device on the shop LAN can commit forged bills under any cashier's name with no login, and the override audit rows compound with SEC-C6 (client-asserted approver). This is SEC-A4's failure mode surfacing at the store tier before the sync pipe even exists. **Impact:** critical once any real store runs it — financial record forgery, GST series consumption, poisoned fraud analytics. **Mitigation (either, before first pilot):** (a) simplest — move the route below `requireAuth` and derive `tenantId`/`cashierId` from the session exactly like `/bills` (the POS already persists its session token, so the offline queue can drain with it after re-login); or (b) the D14-aligned answer — a per-counter device token in a `device` table, required as a header, with server-side cross-checks that the store belongs to the device's tenant and `cashierId`/`shiftId` belong to that store. Either way, reject body identities that don't resolve within the authenticated tenant.
**SEC-B · Local data security at the shop** *(perf Phase 0; ship Phase 1; Hub auth Phase 2)* **SEC-B · Local data security at the shop** *(perf Phase 0; ship Phase 1; Hub auth Phase 2)*
The counter's SQLite buffer is the whole business in one copyable file — items, costs, margins, full sales history, party ledgers, customer phone numbers — on a PC that staff, technicians, and the cyber-café repair guy all touch; and shop PCs get stolen, resold, or repossessed still enrolled. **Impact:** high — cost/margin data to a competitor, customer list to a rival, DPDP exposure, plus a rogue enrolled device that can still sync. **Mitigation:** **SQLCipher full-database encryption** on every counter buffer and the store tier (256-bit random key per device; verify the < 150 ms budget survives in the Phase-0 spike expected single-digit % overhead), the key **wrapped by Windows DPAPI (machine scope)** so a copied file is useless off the machine and **escrowed to a per-tenant cloud key** so a support-led recovery can unlock a salvaged DB. An owner-facing **"deactivate device"** action revokes the device identity immediately and queues a **remote wipe** that executes if the machine ever comes online. Be honest in the threat-model doc: this stops the *copied file* and the *stolen PC*, not a person at the running app that person is handled by roles + audit, not crypto. Store-tier and Hubcounter LAN traffic reuse the device-identity mutual auth (the store's flat LAN has the CCTV DVR and the neighbour's laptop on it). The counter's SQLite buffer is the whole business in one copyable file — items, costs, margins, full sales history, party ledgers, customer phone numbers — on a PC that staff, technicians, and the cyber-café repair guy all touch; and shop PCs get stolen, resold, or repossessed still enrolled. **Impact:** high — cost/margin data to a competitor, customer list to a rival, DPDP exposure, plus a rogue enrolled device that can still sync. **Mitigation:** **SQLCipher full-database encryption** on every counter buffer and the store tier (256-bit random key per device; verify the < 150 ms budget survives in the Phase-0 spike expected single-digit % overhead), the key **wrapped by Windows DPAPI (machine scope)** so a copied file is useless off the machine and **escrowed to a per-tenant cloud key** so a support-led recovery can unlock a salvaged DB. An owner-facing **"deactivate device"** action revokes the device identity immediately and queues a **remote wipe** that executes if the machine ever comes online. Be honest in the threat-model doc: this stops the *copied file* and the *stolen PC*, not a person at the running app that person is handled by roles + audit, not crypto. Store-tier and Hubcounter LAN traffic reuse the device-identity mutual auth (the store's flat LAN has the CCTV DVR and the neighbour's laptop on it).

Loading…
Cancel
Save