Files
parking_solution/wiki/concepts/threat-model.md
T
julian a20400c2c5 fix: record subscription sale as a signed payment (close off-book hole)
Creating a priced subscription wrote only the mutable `subscriptions`
master row and appended NOTHING to the signed ledger — so the cash an
operator collected showed in the live feed, drawer, and shift Z-report
nowhere, leaving no signed trace. A booth operator could sell
subscriptions and pocket the money untraceably — the exact
operator-as-adversary path the append-only signed ledger exists to close.
Found live: 3 priced subscriptions (27,000 ALL) had zero payment events.

Selling a priced subscription now appends a signed `payment` event at
create time: amount = priceMinor x months (full multi-month prepay),
operator-chosen tender (cash->drawer / card->bank), payload
{ subscriptionSale: true, permitId, operator, months }. Folds into the
shift Z-report/drawer with no new summing logic; the feed badges it
"subscription sale" and resolves the holder name. The create response
returns the recorded { sale }; subscriptionRoutes now takes the EventLog
and ShiftService.

Not hard-gated on an open shift (a sale can happen outside the booth money
path) — it warns instead. The 3 historical off-book sales are not
back-fillable (append-only forbids forging dated events) — reconcile via
cash_movement or a Z-report note.

Verified against a copy of the live DB with the real signing modules:
signed payment appended, hash-chain still verifies, lands in shift cash
totals. Build + lint 12/12.

Wiki: subscription "Collecting the fee" deferred -> BUILT (+ the off-book
hole and why); shift sale-folds-in; threat-model worked example
("store the price != account for the sale").

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-20 15:46:17 +02:00

3.1 KiB

type, tags, sources, updated
type tags sources updated
concept
parking
security
foundational
parking-system-architecture
2026-06-20

Threat Model

The second foundational force (with offline-first). The central insight is a reframing of who the adversary is. (See parking-system-architecture §3.)

The key reframing

Early thinking focused on protecting the database at rest — SQLCipher, LUKS, BitLocker, TPM-sealed keys. All of that defends against an outsider who steals the machine or boots from external media.

That is the wrong primary threat. The most likely adversary is the legitimate operator at the booth. While the app runs, the database is decrypted in memory and the operator has full authorised access through the app. Encryption does nothing against the classic parking fraud: take the cash, then void/delete the entry/exit record so the books balance.

Consequences

The controls that actually address insider/operator fraud are different in kind:

  • append-only-event-chain — events appended, never edited/deleted; a "void" is itself a recorded event, hash-chained, and atecc608-signed (unforgeable).
  • reconciliation against an authority the operator can't alter — this is what remote sync really is: a fraud-control mechanism, not just a backup.
  • disk-os-hardening still worthwhile (defeats boot-from-USB) but not the main event; with LUKS in place, SQLCipher is optional defence-in-depth.

The same reframing recurs at the device layer: the uhppote-controller's real problem is unauthenticated commands (uhppote-udp-protocol), addressed by detection (event-log-ingestion) or prevention (esp32-custom-controller).

Worked example — "store the price" ≠ "account for the sale" (found + fixed 2026-06-20). Every money-taking action must append a signed payment event, or it is invisible to reconciliation. A concrete miss: selling a subscription wrote only the mutable subscriptions master row (the agreed price) and appended nothing to the ledger, so the cash the operator collected showed up in the feed/drawer/Z-report nowhere — a clean off-book channel (three real sales, 27,000 ALL, untraceable). The fix is the textbook control: append a signed payment (subscriptionSale: true) at sale time so it folds into the shift like any taking. The lesson generalises: whenever a feature records an amount in a mutable table, ask "where is the signed event that says money changed hands?" — a price in master data is not an accountable transaction. See subscription "Collecting the fee".

Direction shift: the system is heading toward fully unmanned operation — no operator, no booth (autonomous-direction). That removes the booth-operator as the primary adversary, but swaps in unattended-machine threats (tailgating, plate spoofing, physical tampering, forced entry). The append-only signed log + reconciliation controls carry over; the emphasis moves from "catch the cashier" to "trust the automated record and detect tampering."