Appearance
Theseus layer-1 unlock
Approved: 17 September 2026 (#151)
Wantok staff unlock Theseus before any site QR. This is not a login to a customer SafeCall admin user. Layer 1 must not live on the customer box.
Purpose and non-goals
Purpose
- Gate the app so only someone with the shared Wantok staff passphrase can reach the post-unlock home.
- Name where the production hash lives and how debug builds differ from release.
Non-goals
- Per-person allowlist, OIDC / IdP, Hansel
users.json. - Per-install identity on the customer box.
- Putting the production passphrase or production PHC string in git.
- Attestation that the device is untampered.
Approved decisions
| Choice | Value |
|---|---|
| Mechanism | One shared staff passphrase |
| Hash | Argon2id PHC string ($argon2id$v=19$m=…) via hashlib argon2Verify |
| Dev/test passphrase | theseus-dev (public, documented, not a secret) |
| Dev/test hash | Fast Argon2id (m=16,t=1,p=1) from public salt theseus-dev-salt in unlock_hash.dart |
| Release | --dart-define=THESEUS_UNLOCK_HASH=… only. Missing define fails closed. Never falls back to theseus-dev in kReleaseMode. |
| Production PHC | NAS /srv/sourcectl/secrets/theseus/unlock.hash |
| Production passphrase | Apple Passwords (not NAS plaintext, not git) |
| Local flag | shared_preferences after success (convenience, not attestation) |
| Pairing UI | Scan site QR is behind this gate (T3). The unlock screen's only scanner reads the RTLS test QR, which opens the restricted tester mode (see below). |
Owner: Thomas Minitsios. Store: the approved Keryx / sourcectl secret store. Do not invent a second store.
Dev vs release
Debug / test builds derive a fast Argon2id PHC from the public passphrase theseus-dev and salt theseus-dev-salt when THESEUS_UNLOCK_HASH is unset.
Release builds (kReleaseMode) require the dart-define. Empty or missing define: unlock always fails.
Mint a production PHC (do not echo the passphrase into logs or git):
bash
cd theseus
# Type the passphrase, then newline. Stdout is the PHC string only.
dart run tool/hash_unlock.dartCopy stdout to the NAS path above. Internal/release flutter build must pass --dart-define=THESEUS_UNLOCK_HASH=$(cat /path/to/unlock.hash) from that file. The hasher uses stronger Argon2 parameters than the committed test hash; parameters live in the PHC string, so the verifier does not hard-code them.
Operator row: OA-AT-unlock (done 17 September 2026: NAS unlock.hash; passphrase in Apple Passwords only).
Local persistence
After a successful verify, Theseus sets a boolean in shared_preferences. Cold start without that flag shows the unlock screen, or the tester mode when an RTLS test QR is stored (below). Lock Theseus in the start screen menu clears the flag and returns to unlock; so does clearing app data. Locking stops the ops stream and drops the in-memory inbox. It keeps the site pairing and the RTLS test QR.
Hospital tester mode (RTLS test QR)
A hospital tester runs the RTLS field test without the Wantok passphrase. Under the passphrase form, the unlock screen has a secondary button, Hospital tester? Scan the test QR, with a Czech line under it. That scanner accepts one QR type, safecall.theseus.rtls.v1, minted on the box's Mobile Access page. It rejects the site pairing QR (safecall.theseus.pair.v1), Keryx and Nav QRs with a message in Czech and English.
A scanned test QR opens the restricted tester mode:
| Shown | Not shown |
|---|---|
| The RTLS testing tool only, as the app's home | Ops inbox, site pairing, site map, BLE confirm, route authoring |
- Back does not leave the tool. The menu item Leave the test (with a confirmation) clears the stored QR and returns to the unlock screen.
- The QR carries a per-channel phone token (
rp_…). It only reads that channel's view and posts that channel's actions on the relay. It grants nothing on the box, and the admin revokes it on Mobile Access. - Release builds accept the QR only when its relay URL is
httpsand its host is in the compile-timeTHESEUS_RTLS_RELAY_HOSTSlist (defaultupdates.wantok.cz). Debug builds also accept a local dev relay (localhost,127.0.0.1,10.0.2.2). - Staff who unlock with the passphrase see the same tool as the RTLS testing tool card on the start screen. The card opens a stored test QR, or the scanner when none is stored. With staff unlock set, the app always opens the start screen, even when a test QR is stored.
Storage keys: theseus_rtls_pairing (the QR), theseus_rtls_lang (CS / EN), theseus_rtls_notices (handled notice ids). The Android manifest sets android:allowBackup="false", so these do not go to Google backups.
Threat notes
| Issue | Treatment |
|---|---|
| Stolen APK contains the production PHC | Offline brute-force of the passphrase. Use a long random passphrase in Apple Passwords. |
| Rooted device / backup edits prefs | Flag is convenience. A tampered client can skip the gate. Accepted for this internal MVP. |
| Lost unlocked phone | Stays unlocked until someone locks it or app data is cleared. Site tokens and ops subscribe follow T3 / T4. |
| Release built without dart-define | Fail closed: unlock never succeeds. |
| Debug hash in a staff build | theseus-dev is public. Do not ship kReleaseMode with the debug fallback. |
| Site QR before unlock | No site pairing UI on the unlock screen. Layer 2 stays closed until T3. |
| Test QR before unlock | Opens only the RTLS testing tool (relay channel, phone token scoped to that channel, revocable, expires within 72 h). No ops inbox, no site token. |
| Forged test QR pointing elsewhere | Release builds refuse relay hosts outside THESEUS_RTLS_RELAY_HOSTS and any http relay. |
Current gap
T3 (site pairing) and T4 (ops inbox) stay behind this gate. Do not show the site QR scanner or the inbox on the unlock screen or on the locked-out path. The RTLS test QR scanner is the one exception, and it opens only the tester mode.