Skip to content

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 ​

ChoiceValue
MechanismOne shared staff passphrase
HashArgon2id PHC string ($argon2id$v=19$m=…) via hashlib argon2Verify
Dev/test passphrasetheseus-dev (public, documented, not a secret)
Dev/test hashFast 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 PHCNAS /srv/sourcectl/secrets/theseus/unlock.hash
Production passphraseApple Passwords (not NAS plaintext, not git)
Local flagshared_preferences after success (convenience, not attestation)
Pairing UIScan 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.dart

Copy 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:

ShownNot shown
The RTLS testing tool only, as the app's homeOps 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 https and its host is in the compile-time THESEUS_RTLS_RELAY_HOSTS list (default updates.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 ​

IssueTreatment
Stolen APK contains the production PHCOffline brute-force of the passphrase. Use a long random passphrase in Apple Passwords.
Rooted device / backup edits prefsFlag is convenience. A tampered client can skip the gate. Accepted for this internal MVP.
Lost unlocked phoneStays unlocked until someone locks it or app data is cleared. Site tokens and ops subscribe follow T3 / T4.
Release built without dart-defineFail closed: unlock never succeeds.
Debug hash in a staff buildtheseus-dev is public. Do not ship kReleaseMode with the debug fallback.
Site QR before unlockNo site pairing UI on the unlock screen. Layer 2 stays closed until T3.
Test QR before unlockOpens 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 elsewhereRelease 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.