diff --git a/wiki/concepts/hardware-signer-options.md b/wiki/concepts/hardware-signer-options.md new file mode 100644 index 0000000..167edef --- /dev/null +++ b/wiki/concepts/hardware-signer-options.md @@ -0,0 +1,90 @@ +--- +type: concept +tags: [parking, security, integrity, crypto, hardware, threat-model] +sources: [] +updated: 2026-07-02 +--- + +# Hardware signer options (non-extractable ledger signing key) + +Where the **[[append-only-event-chain|signed ledger]]'s** private signing key should live so that an +adversary who **owns the host** — including one who decrypts the disk via the [[disk-os-hardening]] +physical-tamper chain — still cannot **forge** the ledger. This is the still-open +[[open-questions|open-question #6]], reframed once it became clear (2026-07-02) that **no secure +element is on-site** and the [[atecc608]] isn't even the right part for a PC. + +## The problem in one line + +Today signing uses the software `SoftwareSigner` — **HMAC-SHA256, key in `EVENT_SIGNING_KEY`, an env +var on the host disk**. HMAC is symmetric: the same secret signs *and* verifies, and it sits on the +box. So the chain is tamper-**evident** (a blind editor without the key breaks it, and `verifyChain()` +pinpoints where) but **not unforgeable** — anyone who reads the key re-signs a doctored chain and it +verifies clean. The fix is a signer whose **private key is non-extractable**: the host can ask it to +sign, but can never read the key. That is a property of an HSM / smartcard / TPM — **not** of anything +that merely *stores* a key. + +## The four options + +### 1. USB HSM — Nitrokey HSM 2 / SmartCard-HSM *(recommended target)* +A ~€50 USB device with a non-extractable EC key (secp256r1 — same curve family as the ATECC608). The +host signs each event over **PKCS#11**; the key never leaves the token. It's essentially "the +ATECC608, but on USB instead of soldered", so it drops into the existing `signer.ts` seam (the +`keyId` field + the `TODO(atecc608)` public-key-verifier hook). +- **License fit:** OpenSC (the PKCS#11 stack) is LGPL/permissive — no vendor lock-in, matches the + all-MIT/Apache/BSD [[technology-stack|stack constraint]]. +- **Downside:** it's *removable* — an operator can pocket it. But that fails **closed** (no token → + can't sign → visibly noticed), and it can be epoxied / locked inside the case. + +### 2. YubiKey (PIV or OpenPGP applet) +Also a non-extractable EC/RSA key over PKCS#11; very robust, widely deployed; works the same way as +the Nitrokey for this purpose. Slightly more oriented to human 2FA than to an always-present signing +oracle, and the vendor stack is less fully-open than OpenSC. **~$50–70.** Fine as a substitute for +option 1. + +### 3. Reuse the on-board TPM 2.0 *(recommended interim — free)* +The booth PC already has a TPM (it's what seals LUKS — [[tpm]]). A TPM can also hold a non-extractable +signing key and sign over it. **Zero extra hardware, closes the "key in a plaintext env file" hole +immediately.** +- **The nuance:** don't reuse the *LUKS* sealing arrangement. That key is PCR-7-sealed, and the + battery-pull → live-USB → PCR-7 chain ([[disk-os-hardening]]) defeats PCR-7-only policies. Bind the + **signing** key to the TPM **without a PCR policy** (or with a PIN) so it's about non-extractability, + not boot-state — then an attacker who decrypts the disk still can't pull it. +- **Weaker than a dedicated HSM** against a sophisticated bus-sniffing attacker, but far stronger than + today's on-disk HMAC. + +### 4. Plain USB "sentinel" / dongle *(the trap — avoid)* +A generic USB flash drive holding a key file, or a license-dongle that only gates "is this USB +present". **Useless here:** if the key is *readable* off the stick, the host-owner copies it, exactly +like the env var. Presence-gating is not integrity. Only a device that **signs internally** delivers +non-extractability. Do not go this route. + +## Recommendation + +1. **Now (free):** move signing to a **TPM-held key** (option 3, no PCR policy) — kills the + plaintext-key-on-disk exposure using hardware already present. +2. **Target (purchasable):** a **USB HSM (Nitrokey HSM 2)** as the concrete stand-in for the still- + planned [[atecc608]] — same non-extractable-EC model, fits `signer.ts`, open tooling; physically + lock it in the case. +3. **[[atecc608]]** stays reserved for the **deferred** [[esp32-custom-controller]] (embedded), not + the PC host. + +Whichever is chosen, the code seam already exists: each event stores its `keyId`, so a swap is a new +`Signer` impl and **old events stay verifiable** under their original key — no migration of history. + +## The mental model to keep + +Against the physical adversary who opens the case, **the only real protection for the financial +record is a signing key even root can't read.** That is an HSM/smartcard/TPM property, never a +USB-storage property — and it's precisely what's missing today. Note the layering: [[disk-os-hardening]] +raises the cost of *reaching* the disk; a hardware signer makes the ledger unforgeable *even after* +the disk is reached; and [[reconciliation]] against records the box doesn't hold is the backstop that +survives a fully-owned host. They are complements, not substitutes. + +## Relates +- [[append-only-event-chain]] — the ledger this key signs; why software signing isn't enough. +- [[atecc608]] — the originally-specified secure element (upcoming; embedded, not PC). +- [[tpm]] — the on-board part; option 3, and the LUKS-sealing analysis. +- [[disk-os-hardening]] — the physical-tamper chain that makes a non-extractable key necessary. +- [[open-questions]] #6 (secure-element integration), #7 (JWT symmetric→asymmetric — same key-custody + argument for auth tokens). +- [[threat-model]] / [[reconciliation]] — the adversary and the backstop control. diff --git a/wiki/concepts/threat-model.md b/wiki/concepts/threat-model.md index 2d7fd3a..b9b6e3b 100644 --- a/wiki/concepts/threat-model.md +++ b/wiki/concepts/threat-model.md @@ -26,7 +26,10 @@ authorised access *through the app*. Encryption does nothing against the classic 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). + recorded event, hash-chained, and signed. ⚠ Signing is **software today** (key on the host disk) → + tamper-*evident* but forgeable by a host owner; a non-extractable **hardware signer** + ([[hardware-signer-options]]; [[atecc608]] upcoming) is what makes it truly *unforgeable*. Which is + why the load-bearing insider control is reconciliation, next. - **[[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**; diff --git a/wiki/decisions/disk-os-hardening.md b/wiki/decisions/disk-os-hardening.md index 6307784..e6f19a2 100644 --- a/wiki/decisions/disk-os-hardening.md +++ b/wiki/decisions/disk-os-hardening.md @@ -20,14 +20,27 @@ across the first real provisioning (2026-06-23) and the firmware-update episode > **not** replace reconciliation, and it cannot stop a *legitimate, logged-in* operator from > committing fraud through the app (that's what the signed ledger + reconciliation are for). +> ⚠ **Caveat on the ledger's tamper-resistance (2026-07-02).** The "signed event chain" above is +> **software-signed today** (HMAC, key in `EVENT_SIGNING_KEY` on the host disk) — the [[atecc608]] +> secure element is [[open-questions|upcoming, not present]]. So a physical adversary who *decrypts +> the disk* (see the tamper chain below) reads the signing key and can **forge/re-sign a doctored +> ledger undetectably** — the chain does not save you against a host-owner until a non-extractable +> hardware signer ([[tpm|TPM]] / USB HSM — [[hardware-signer-options]]) is wired. Until then the disk's +> confidentiality/integrity leans harder on the controls below, and the residual backstop against +> forgery is **external reconciliation** against records the box doesn't hold (payments, an offsite +> backup, a separate witness), not the on-disk signature. + ## What it defends against The appliance sits on-site, physically reachable by the [[threat-model|booth operator (the primary adversary)]] and by an outsider who can open the case. Without host hardening, either can: - **Pull the SSD** and read/alter the SQLite ledger offline → FDE (LUKS) defeats this. -- **Boot a live USB** to mount and edit the disk → Secure Boot + TPM-sealing (PCR 7) defeats booting - a tampered/unsigned kernel; FDE keeps the data unreadable. +- **Boot a live USB** to mount and edit the disk → **the BIOS boot-order/boot-menu password is the + load-bearing control here**, NOT Secure Boot. Secure Boot happily runs a *signed* Ubuntu live USB, + and PCR-7-only sealing can't distinguish it from our own boot (same signing authorities → same + PCR 7 → the TPM would unseal). So confidentiality rests on the operator being unable to *select* + the USB. See the physical-tamper chain below (a CMOS reset strips that password). - **Edit the GRUB cmdline** (`init=/bin/bash`) for a no-login root shell on the *decrypted* disk → the GRUB edit-lock defeats this (the TPM seal does NOT — see below). - **Escalate from the operator login** (sudo, `docker`/`lxd` groups) → the unprivileged-operator @@ -74,6 +87,50 @@ turns a routine "security update" into a booth-availability risk: > false safety signal). Before any firmware change, prove a *typed* passphrase still unlocks the disk > with `--disable-external-tokens` — see [[appliance-provisioning]] §4a. +## Physical-tamper chain & accepted risks (traced 2026-07-02, Dell OptiPlex 7070) + +The BIOS admin password gates Setup **and** the one-time boot menu (verified: choosing a USB device +at F12 prompts for the password). That closes the live-USB path — *while the password holds*. The +uncomfortable finding is that the password is a **soft control** against a case-opening adversary: + +1. **CMOS reset** (pull the coin cell, or the on-board PSWD/CMOS-clear jumper — Dell documents its + location) → BIOS *settings* return to factory defaults: **admin password cleared, boot menu + open**. It does NOT wipe the Secure-Boot key databases (PK/KEK/db/dbx live in SPI-flash NVRAM, + not the battery-backed RTC), and the 7070's factory default is **Secure Boot = Enabled**. So + after the reset Secure Boot comes back **on, same MS keys** → **PCR 7 reconstructs to the same + value.** +2. **Boot a signed Ubuntu live USB** (now selectable). Same signing authorities → PCR 7 matches the + sealing policy → the attacker runs `cryptsetup`/`systemd-cryptsetup` and the **TPM releases the + LUKS key**. Disk decrypts; they have **root on the decrypted filesystem**, including the ledger. + - Note the asymmetry: if instead they *disable* Secure Boot in the now-unlocked BIOS, PCR 7 + **changes** → the TPM refuses → the box drops to the slot-0 passphrase prompt they don't have. + So *disabling* Secure Boot locks them out; leaving it at the reset default lets them in. This is + the PCR-7-only "same-signer" weakness (systemd docs recommend PCR 7 **+ a PIN** to close it). + +**Net:** a battery-pull alone yields nothing (disk stays sealed), but **battery-pull → live-USB → +PCR-7 unseal** is a realistic chain to **root on the decrypted data**. What it costs the attacker: a +screwdriver and a signed USB. What still holds after it: they never get the **escrowed slot-0 +passphrase**, and (the point of the caveat above) the ledger's forgery-resistance depends on the +signer — with today's **on-disk HMAC key they CAN forge the ledger**; only a hardware signer +([[hardware-signer-options]]) would keep the financial record unforgeable through this. + +**Accepted risks (named, not silently "covered"):** + +- **PCR-7 same-signer unseal via CMOS reset** (above). *Not* mitigated by the current config. Real + fixes both cost the thing we optimised for: a **TPM PIN** (`--tpm2-with-pin=yes`) closes it but + **kills unattended boot** (someone types a PIN each power event); **custom Secure-Boot keys / more + PCRs** close it but reintroduce re-seal churn on kernel/shim updates (the exact thing PCR-7-only + avoids). Deferred decision — accept for now; the primary control remains reconciliation + escrowed + backups, not disk confidentiality. +- **Unsigned initramfs (evil-maid).** `/boot` is unencrypted and Ubuntu does **not** sign the + initramfs (Secure Boot verifies shim→GRUB→kernel; PCR 7 doesn't measure the initrd). The initramfs + is exactly the code that receives the LUKS key, so a maid who tampers it and waits one boot can + harvest the key. Accepted: needs repeated privileged physical access, and a hardware signer would + still keep the ledger unforgeable even from a fully-owned host. +- **Operator USB at an auto-logged-in session** — unverified what the unprivileged operator account + can read on the host (e.g. can it reach the Docker-mounted `/data`?). **TODO: verify** the operator + can't read the DB volume or `EVENT_SIGNING_KEY` from its own login. + ## Where the commands live This page is the rationale. The **verified, run-on-real-hardware commands** are in @@ -89,4 +146,6 @@ root-capable remote agent — see [[fleet-deployment-komodo]] (bind to the NetBi - [[threat-model]] — the operator-adversary framing this hardening serves. - [[reconciliation]] / [[append-only-event-chain]] — the **primary** anti-fraud control this complements, never replaces. +- [[hardware-signer-options]] — TPM / USB-HSM / ATECC608 options for a non-extractable ledger + signing key (the missing piece this page's tamper chain exposes). - [[fleet-deployment-komodo]] — Periphery as part of the trusted computing base. diff --git a/wiki/decisions/open-questions.md b/wiki/decisions/open-questions.md index 2ed0ddc..3af3d23 100644 --- a/wiki/decisions/open-questions.md +++ b/wiki/decisions/open-questions.md @@ -40,10 +40,19 @@ procurement. (See [[parking-system-architecture]] §10.) but **unverifiable after the machine dies**). **Restore is admin-only/out-of-band** (operator-adversary surface — [[threat-model]]). See [[backup-recovery]], [[fleet-deployment-komodo]], [[disk-os-hardening]], [[reconciliation]] (#4). -6. **Secure-element integration.** Confirm [[atecc608]] wiring/usage on the host (event - signing). The [[esp32-custom-controller]] command-authentication use is **deferred — not - being implemented for now** (access control is the [[dingtian-relay]] behind - [[network-isolation]]); revisit only if prevention-grade device auth becomes a requirement. +6. **Secure-element integration.** _(Updated 2026-07-02: no secure element is on-site today.)_ + Event signing currently runs on the **software `SoftwareSigner`** (HMAC-SHA256, key in + `EVENT_SIGNING_KEY` — an env var **on the host disk**). So the ledger is tamper-EVIDENT but + **not** unforgeable by anyone who owns the host: a case-opening adversary who decrypts the disk + reads the key and can re-sign a doctored chain (see [[append-only-event-chain]] "pull-the-disk", + [[disk-os-hardening]] physical-tamper chain). The **[[atecc608]] is UPCOMING, not present** — and + it isn't even the right host part: on a PC appliance the realistic non-extractable host signer is + the **[[tpm|TPM 2.0]]** the box already has, or a **USB HSM** (Nitrokey HSM 2 / SmartCard-HSM); + reserve the ATECC608 for the (deferred) [[esp32-custom-controller]]. The concrete menu + + recommendation (TPM interim → USB-HSM target) is in [[hardware-signer-options]]. The + controller command-authentication use is **deferred — not being implemented for now** (access + control is the [[dingtian-relay]] behind [[network-isolation]]); revisit only if prevention-grade + device auth becomes a requirement. 7. **JWT signing: symmetric vs. asymmetric key.** _(Raised by the commit security review, not the source doc.)_ Auth currently uses a symmetric HMAC secret (`@fastify/jwt`, see [[local-jwt-auth]]) — the same secret signs *and* verifies, so it must live on every host that diff --git a/wiki/decisions/standing-decisions.md b/wiki/decisions/standing-decisions.md index 0c3e995..ac7b641 100644 --- a/wiki/decisions/standing-decisions.md +++ b/wiki/decisions/standing-decisions.md @@ -25,9 +25,11 @@ The decisions treated as settled in the design notes. (See [[parking-system-arch deny-by-default native surface that fits [[threat-model|the booth-operator threat model]]. The shell stays **thin**: all privileged logic remains in [[fastify]]. One open dependency — the appliance's WebKitGTK version (see [[open-questions]] #11). -- **Integrity:** append-only, hash-chained, [[atecc608]]-signed event log - ([[append-only-event-chain]]); **[[reconciliation]] is the anti-fraud control**; encryption - protects only at-rest (see [[threat-model]]). +- **Integrity:** append-only, hash-chained, **software-signed** event log + ([[append-only-event-chain]]) — hardware-backed signing (a non-extractable key in the + **[[tpm|TPM]]** or a **USB HSM**; the [[atecc608]] is [[open-questions|upcoming, not present]]) is + the target that makes it unforgeable by a host owner ([[hardware-signer-options]]). **[[reconciliation]] + is the anti-fraud control**; encryption protects only at-rest (see [[threat-model]]). - **Access control:** the **[[dingtian-relay]]** relay+input controller, on an **isolated VLAN** ([[network-isolation]]). Chosen because its **inputs are decoupled from its relays**, enabling host-in-the-loop ticket-first entry — the resolution to [[access-controller-button-flow]]. diff --git a/wiki/entities/atecc608.md b/wiki/entities/atecc608.md index ed40c09..b59ad13 100644 --- a/wiki/entities/atecc608.md +++ b/wiki/entities/atecc608.md @@ -2,26 +2,38 @@ type: entity tags: [parking, hardware, security, crypto] sources: [parking-system-architecture] -updated: 2026-06-14 +updated: 2026-07-02 --- # ATECC608 (secure element) An inexpensive **secure element** holding a signing key that **cannot be extracted, even by -someone who owns the machine**. The keystone of integrity in this system. (See +someone who owns the machine**. The design's intended keystone of ledger integrity. (See [[parking-system-architecture]] §3, §7.) -Two distinct uses: +> **⚠ STATUS — UPCOMING, NOT PRESENT (2026-07-02).** No ATECC608 (nor any secure element) is on +> the booth today. Event signing runs on the **software `SoftwareSigner`** (HMAC-SHA256, key in +> `EVENT_SIGNING_KEY`, an env var on the host disk). Consequence: the [[append-only-event-chain]] +> is tamper-**evident** but **not** unforgeable by an adversary who owns the host — they can read +> the key and re-sign a doctored chain ([[disk-os-hardening]] physical-tamper chain). Do not describe +> the ledger as "hardware-signed / unforgeable" in present tense. This is the still-open +> [[open-questions|open-question #6]]. -1. **Host-side event signing.** Each event in the [[append-only-event-chain]] is signed by the - ATECC608 on the host machine. This is what makes the hash chain **unforgeable** rather than - merely self-consistent. -2. **Custom controller command authentication.** On the [[esp32-custom-controller]], it holds - the key(s) for [[challenge-response-auth]] — generated on-chip, non-extractable, so popping - the cabinet and dumping flash yields nothing usable. +Two distinct **intended** uses: -Confirming ATECC608 wiring/usage on both ends is [[open-questions]] #6. Listed in the [[bom]] -on the host machine. +1. **Host-side event signing.** The design has each event in the [[append-only-event-chain]] signed + by a non-extractable key so the hash chain is **unforgeable** rather than merely self-consistent. + **Caveat:** the ATECC608 is an external I²C part native to embedded boards, **not a PC component** — + on the PC-based booth appliance the realistic non-extractable host signer is the **[[tpm|TPM 2.0]]** + already on the machine, or a **USB HSM** (Nitrokey HSM 2 / SmartCard-HSM). See + [[hardware-signer-options]] for the full menu + the TPM-interim → USB-HSM-target recommendation. +2. **Custom controller command authentication.** On the (deferred) [[esp32-custom-controller]], it + would hold the key(s) for [[challenge-response-auth]] — generated on-chip, non-extractable, so + popping the cabinet and dumping flash yields nothing usable. This use is **deferred** (access + control is the [[dingtian-relay]] behind [[network-isolation]]). + +Confirming a real secure-element signer on the host is [[open-questions]] #6. Listed (aspirationally) +in the [[bom]]; treat as a future line item until procured. > **Platform caveat (2026-06-21):** the ATECC608 is **not a PC component** — it's an external I²C > secure element you add/solder, native to embedded boards (the [[esp32-custom-controller]]), not to diff --git a/wiki/index.md b/wiki/index.md index fe616bf..1a82db9 100644 --- a/wiki/index.md +++ b/wiki/index.md @@ -1,13 +1,13 @@ --- type: overview tags: [parking, index] -updated: 2026-06-21 +updated: 2026-07-02 --- # Index Content catalog for the wiki. Start at [[overview]]. Maintained on every ingest. -Counts: 4 sources · 19 entities · 46 concepts · 7 decision records. +Counts: 4 sources · 19 entities · 47 concepts · 7 decision records. ## Overview & navigation - [[overview]] — the top-level synthesis and entry point. @@ -53,7 +53,8 @@ Counts: 4 sources · 19 entities · 46 concepts · 7 decision records. - [[tpm]] — TPM 2.0 hardening: how it works, sealed-LUKS auto-unlock + non-extractable signing key; limits (live-root, bus-sniff) + TPM-vs-ATECC608 by platform; complements, not replaces, reconciliation. ## Concepts — integrity & anti-fraud -- [[append-only-event-chain]] — append-only + hash chain + ATECC608 signing = unforgeable log. +- [[append-only-event-chain]] — append-only + hash chain + signing = unforgeable log (signing is **software today**; hardware signer pending — see below). +- [[hardware-signer-options]] — where the ledger signing key should live (TPM interim → USB-HSM target; ATECC608 upcoming, not on-site) so a host-owner can't forge the chain. - [[reconciliation]] — the real anti-fraud control; what remote sync actually is. - [[disk-os-hardening]] — the *why* of host hardening: LUKS FDE + TPM-sealed auto-unlock (PCR 7) + Secure Boot + GRUB edit-lock + unprivileged operator + firmware/dbx lockdown; secondary control (reconciliation is the main event). Commands → [[appliance-provisioning]]. - [[backup-recovery]] — admin-driven encrypted full-DB backup (local/SMB/SFTP) + DR; signing key escrowed & decoupled from TPM so the ledger survives total hardware loss; restore is admin-only. @@ -107,6 +108,7 @@ Counts: 4 sources · 19 entities · 46 concepts · 7 decision records. - [[subscription]] — recurring plan (e.g. 10,000 ALL/month); RF/QR or plate identity, car-count + max-concurrent, host-in-loop; short-circuits payment. (Renamed from "permit"; time-of-day windows noted, deferred.) - [[opencv-anpr-service]] — host-side vision microservice: ANPR (plate identity) + vehicle verification (anti-plate-spoofing witness); fast-alpr (MIT, YOLOv9+CCT/ONNX) the evaluated recognizer baseline. - [[lane-presence-and-anpr-entry]] — camera vehicle detection → (BUILT) advisory lane busy/free booth lights + (BUILT) the ANPR "bridge" (`anpr-entry.ts`): a subscriber's plate read at the lane admits them via the existing gated subscription flow (match-before-emit; subscriber-only). Measured camera limits; rejected the queue-tracking/livestream ideas. +- [[vision-service-hardening]] — fix/hardening backlog for `apps/vision/` (2026-07-02 reviews): DoS (body-cap, pixel-bomb, event-loop-blocking inference), unauthenticated + operator-writable model weights, `0.0.0.0` default bind, + correctness/hygiene items. Not yet fixed — the to-do list. - [[blocklist]] — barred plates/cards refused at entry (never at exit); signed, attributed. ## Concepts — frontend / operator UI diff --git a/wiki/log.md b/wiki/log.md index 85373a2..f7f0b19 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -2139,3 +2139,71 @@ override-releases-with-attribution, low-confidence-no-warning, own-plate-no-warn operator-issued-entry.md + plate-reconciliation.md; cross-linked from entry-exit-points, capacity-occupancy, index. Preserves "a plate never OPENS a barrier alone — and now never TRAPS a car alone either." + +## [2026-07-01] deploy | Promote dev → stage (d2ab2e0) → park-buzi, pinned TAG=stage-d2ab2e0 + +Merged dev → stage (no-ff, clean — stage content was fully contained in dev). Shipped to the staging +booth: snapshot content-type fix, Active Sessions/modal rework, DB reset CLI, drawer redesign (operator +records / admin reviews), card tender disabled (no POS), operator-issued entry + exit plate-swap +reconciliation. Push to stage triggered CI → built parking-server/vision :stage + :stage-d2ab2e0. Pinned +TAG=stage-d2ab2e0 in komodo/resources.toml on BOTH stage and dev (the ResourceSync's source branch is a +Core-side config, so both agree — see komodo/README.md; they're identical content anyway). + +Migration note: this promotion carries migrations 0018 (drawer:create) + 0019 (session:create). BOTH are +DATA SEEDS, not schema — INSERT OR IGNORE one role_permissions row each for the built-in `operator` role; +idempotent, no CREATE/ALTER, existing data untouched. They apply automatically at container boot +(docker-entrypoint.sh → migrate-runtime.mjs, before the server starts) against the /data volume DB, which +survives the redeploy. Caveat recorded in container-deployment.md: a permission seeded to the built-in +operator role does NOT reach a CUSTOM role — an admin toggles it in Setup → Roles. + +Deploy (operator, in Komodo Core): refresh ResourceSync (TAG diff enables Execute) → Execute → Deploy +(Destroy+Deploy for a clean recreate; parking-data volume persists). Watch for `[migrate] done` in logs. + +## [2026-07-02] query | Physical-tamper of the booth disk + ledger signing reality (ATECC608 is upcoming, not present) + +Q (operator): can a malicious user boot a live Ubuntu / reset the BIOS (coin cell or PSWD jumper) and +get root on the storage? Traced on the actual box (Dell OptiPlex 7070): the BIOS admin password DOES +gate the F12 boot menu (selecting the USB prompts for it), so the live-USB path is closed **while the +password holds**. But a CMOS reset clears the admin password + reopens the boot menu WITHOUT wiping the +Secure-Boot key DBs (SPI-flash NVRAM, not RTC), and the 7070 default is Secure Boot=Enabled → PCR 7 +reconstructs to the SAME value → a signed live Ubuntu (same signing authorities) matches the PCR-7-only +seal → the TPM releases the LUKS key → root on the decrypted disk. Battery-pull alone = nothing; +battery-pull → live-USB → PCR-7 unseal = realistic root-on-data. (Disabling Secure Boot instead CHANGES +PCR 7 → passphrase prompt → locked out; the same-signer default is the hole. systemd docs: PCR 7 + PIN.) + +BIGGER correction surfaced: the ledger is NOT ATECC608-signed today. No secure element is on-site. Signing +runs on the software SoftwareSigner (HMAC, key = EVENT_SIGNING_KEY, an env var on the host disk). So the +chain is tamper-EVIDENT but forgeable by whoever owns the host — the disk-decryption chain above hands +them the key too. The ATECC608 was overstated as present in several pages; it's also the wrong part for a +PC (external I²C, embedded-native) — reserve it for the deferred ESP32; the realistic host signer is the +on-board TPM or a USB HSM. + +Actions: +- NEW concepts/hardware-signer-options.md — four options (USB HSM/Nitrokey HSM 2 [target], YubiKey, reuse + the TPM [free interim, bind signing key with NO PCR policy], plain USB dongle [trap, avoid]) + the + recommendation (TPM now → USB-HSM target; ATECC608 stays for embedded). Notes the signer.ts keyId seam. +- Retag pass ATECC608 → UPCOMING/NOT-PRESENT + "software-signed today, forgeable by host owner" caveat: + entities/atecc608.md (status banner + PC-vs-embedded), append-only-event-chain already honest, + standing-decisions.md, overview.md, threat-model.md, open-questions.md #6 (reframed), index.md. +- disk-os-hardening.md: fixed the live-USB row (BIOS boot-order password is load-bearing, not Secure + Boot — signed live USB runs), added a caveat banner (software signer → disk decryption = ledger + forgery) + a "Physical-tamper chain & accepted risks" section (CMOS-reset chain; accepted risks: + PCR-7 same-signer unseal, unsigned initramfs evil-maid, operator-USB read TODO). +- Verify items for the box: (a) confirm F12/one-time-boot is password-gated (done — it is); (b) after a + CMOS clear does Secure Boot return Enabled? (expected yes on Dell); (c) can the unprivileged operator + login read /data or EVENT_SIGNING_KEY? +- Residual: signer.ts still uses HMAC (no code change this pass); the load-bearing anti-fraud control + remains reconciliation + escrowed offsite backups, NOT on-disk confidentiality/signature. + +## [2026-07-02] review | Vision service (apps/vision/) hardening + fix backlog +Two code reviews of the Python/FastAPI ANPR service (general: bottlenecks/bugs/best-practice, +and a security-focused pass). Filed the findings as a prioritised, not-yet-fixed to-do list at +[[vision-service-hardening]]; cross-linked from [[opencv-anpr-service]] ("consult before touching") +and cataloged in index.md. Headline items: DoS (12MB cap checked *after* the body is buffered; +`cv2.imdecode` pixel-bomb; CPU inference on the async event loop stalling `/health`); +unauthenticated **and** operator-writable model weights → persistent recognition-poisoning +([[threat-model]]); `0.0.0.0`-by-default bind at all three layers; dev compose publishing 8089 on +all interfaces; plus correctness/hygiene (cwd-relative `.env`, `/health` always-200, pre-warm +swallowing failures, unbounded `min_confidence`). Reassurance recorded: a forged image can't open a +barrier (server re-gates at 0.85 + debounce), content-type isn't trusted, non-root, `.env` not baked +into the image. Nothing fixed yet — this is the backlog to work from. diff --git a/wiki/overview.md b/wiki/overview.md index afa8f44..063e7f6 100644 --- a/wiki/overview.md +++ b/wiki/overview.md @@ -25,9 +25,11 @@ deployed on-site at a parking facility. Two forces shape nearly every decision: [[react-vite-spa]] · [[sqlite]] + [[drizzle-orm]] · [[local-jwt-auth]] — all open-licensed to avoid lock-in (cf. rejected [[payload-cms]], [[refine]], [[logto-zitadel-oidc]]). The operator UI ships as a thin **[[desktop-shell-tauri|Tauri v2]]** kiosk shell (chosen over Electron). -- **Integrity** is the heart of it: an [[append-only-event-chain]] (hash-chained, [[atecc608]]- - signed) plus external [[reconciliation]] — *that's* what remote sync really is. Encryption at - rest ([[disk-os-hardening]]) defends a secondary threat. +- **Integrity** is the heart of it: an [[append-only-event-chain]] (hash-chained, signed) plus + external [[reconciliation]] — *that's* what remote sync really is. Signing is **software today** + (key on-disk → forgeable by a host owner); a non-extractable **hardware signer** (TPM / USB-HSM; + the [[atecc608]] is upcoming) is the pending fix — [[hardware-signer-options]]. Encryption at rest + ([[disk-os-hardening]]) defends a secondary threat. - **Devices** sit behind a [[device-adapter-pattern]] (swap hardware → new adapter only), with the [[barrier-not-a-door]] safety principle keeping physical safety in barrier-operator firmware. - **Access control** today is the **[[dingtian-relay]]** relay+input controller behind