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>
@ -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 <150msbudgetsurvivesinthePhase-0spike—expectedsingle-digit%overhead),thekey**wrapped by Windows DPAPI (machine scope)**soacopiedfileisuselessoffthemachineand**escrowed to a per-tenant cloud key**soasupport-ledrecoverycanunlockasalvagedDB.Anowner-facing**"deactivatedevice"**actionrevokesthedeviceidentityimmediatelyandqueuesa**remote wipe**thatexecutesifthemachineevercomesonline.Behonestinthethreat-modeldoc:thisstopsthe*copied file*andthe*stolen PC*,notapersonattherunningapp—thatpersonishandledbyroles+audit,notcrypto.Store-tierandHub↔counterLANtrafficreusethedevice-identitymutualauth(thestore'sflatLANhastheCCTVDVRandtheneighbour'slaptoponit).
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 <150msbudgetsurvivesinthePhase-0spike—expectedsingle-digit%overhead),thekey**wrapped by Windows DPAPI (machine scope)**soacopiedfileisuselessoffthemachineand**escrowed to a per-tenant cloud key**soasupport-ledrecoverycanunlockasalvagedDB.Anowner-facing**"deactivatedevice"**actionrevokesthedeviceidentityimmediatelyandqueuesa**remote wipe**thatexecutesifthemachineevercomesonline.Behonestinthethreat-modeldoc:thisstopsthe*copied file*andthe*stolen PC*,notapersonattherunningapp—thatpersonishandledbyroles+audit,notcrypto.Store-tierandHub↔counterLANtrafficreusethedevice-identitymutualauth(thestore'sflatLANhastheCCTVDVRandtheneighbour'slaptoponit).