Internal service token: At startup, Clarity creates a never-expiring JWT for the user clarity_service with role admin and stores it in the CLARITY_TOKEN env var. Child processes inherit this and use it for internal API calls.
Source: clarity:backend/src-tauri/src/main.rs:1619-1621
Verified from clarity:backend/src-tauri/src/auth.rs and clarity:backend/src-tauri/src/main.rs.
| Capability | read-only |
read-write |
admin |
|---|---|---|---|
| GET /me | ✅ | ✅ | ✅ |
| POST /fast_query (read) | ✅ | ✅ | ✅ |
| POST /write, /write_fast | ❌ | ✅ | ✅ |
| POST /create_collection | ❌ | ✅ | ✅ |
| POST /pi/onboard_unit | ❌ | ✅ | ✅ |
| POST /admin/user/delete | ❌ | ❌ | ✅ |
| POST /update_password | ❌ | ❌ | ✅ |
| GET /api/admin/backup/* | ❌ | ❌ | ✅ |
| POST /register (create user) | n/a | n/a | ✅ (via admin endpoint) |
NOTE:
read-onlyandread-writediffer only where admin_middleware is used. Theauth_middleware(JWT check only) does not inspect the role field — all valid JWT holders pass. Role enforcement is at the application-level only for admin routes.
Default users (admin, opcuser) are seeded at first startup with passwords sourced from env vars (ADMIN_PASSWORD / OPC_PASSWORD) via the secret pack — plaintext passwords were removed from the seed data in a18d35c, and as of 1027dce the pack is an AES-256-GCM encrypted file (~/.clarity/secrets/secrets-pack.bin), not the OS keychain. If a env var is unset, the existing on-disk pack value is preserved; failing that, ADMIN_PASSWORD falls back to a build-time baked value (option_env!) and OPC_PASSWORD to empty. See API Server § Seed credentials & secret pack. The admin password must be set/changed before deployment.
POST /login is rate-limited at 30 req/60 s per email; exceeding the window locks the account for clarity.auth.lockout_seconds (default 15 min), returning HTTP 429 {"code":"ACCOUNT_LOCKED","retryAfter"} until it expires. clarity:backend/src-tauri/src/main.rs:139-202
| Store | Encryption | Notes |
|---|---|---|
.bin day files |
Not currently encrypted | Raw mmap binary files in data/ directory |
pulse-db.sqlite |
SQLCipher v4 + HMAC | Key from DB_PASSWORD. Each connection sets cipher_compatibility=4, cipher_use_hmac=ON (per-page HMAC-SHA-256), kdf_iter=256000; a startup PRAGMA integrity_check fails-fast on tamper/corruption. Source: clarity:backend/src-tauri/src/sqlite_api/db/mod.rs:128-141, 1233-1259 |
| App secrets (admin/opc/MQTT) | Encrypted file (secret pack) | Single AES-256-GCM + HMAC-SHA256 file ~/.clarity/secrets/secrets-pack.bin (0o600; %APPDATA%\clarity\secrets\ on Windows). Keys derived from hardware fingerprint + build-baked JWT_SECRET → decrypts only on the same machine + same binary. Replaced the OS keychain in 1027dce. (ADMIN_PASSWORD may also be baked into the binary as a build-time fallback.) Source: clarity:backend/src-tauri/src/secure_store.rs:1-194 |
| Python service code | Encrypted .dat + SHA-256 manifest |
Sources XOR+zlib-encrypted at build; SHA-256 hashes embedded; each service verified before spawn. Source: clarity:backend/src-tauri/build.rs:316-525, clarity:backend/src-tauri/src/integrity/mod.rs:1-64 |
Backup archives (.tar.gz) |
Not currently encrypted | Permissions set to 0o600 (Unix). Source: clarity:backend/src-tauri/src/backup/sqlite_backup.rs |
| TLS certificates | N/A (files, not data) | Self-signed, generated per installation; private key in certs/key.pem |
CLARITY_TOKEN env var |
Plaintext in process environment | Internal service JWT visible to child processes |
JWT_SECRET: There is no plaintext default in the source. auth.rs reads the runtime JWT_SECRET env var if set, otherwise falls back to a value baked in at compile time via the env!("JWT_SECRET", ...) macro (sourced by build.sh). Because env! is evaluated at build time, a release binary built without JWT_SECRET exported fails to compile; a built binary then needs no env var at launch. There is no runtime startup panic for a missing JWT_SECRET. Source: clarity:backend/src-tauri/src/auth.rs:28-41
Items verified as confirmed gaps or documented roadmap items from source review. Items marked ⚠ are active risks; items marked 📋 are planned.
| Item | Status | Notes |
|---|---|---|
| Set seed passwords (admin/opc/MQTT) | ⚠ Required on first deploy | Plaintext seed passwords removed (a18d35c); now provisioned from env vars into the encrypted secret-pack file (1027dce). Unset env → existing on-disk value, else build-time baked ADMIN_PASSWORD / empty. secure_store.rs, db/mod.rs:300-313 |
| JWT_SECRET must be set | ⚠ Required at build time | Baked in at compile time via env! (build.sh); runtime env var overrides. No plaintext default; no runtime startup panic. auth.rs:28-41 |
Time-series .bin file encryption |
📋 Roadmap | Currently not encrypted; SQLite is SQLCipher-encrypted |
| Backup archive encryption | 📋 Roadmap | Archives are 0600 but plaintext |
| Database encryption roadmap | 📋 Roadmap | Referenced in system-context boundary label |
| ADK proxy auth | ⚠ Gap | /apps/* and /run_sse routes have no JWT check (source: clarity:backend/src-tauri/src/api/google_adk.rs:102-114) |
| MQTT WebSocket auth | ⚠ Gap | /mqtt WebSocket route has no auth check before forwarding |
| Backup admin route auth | ⚠ Gap | /api/admin/backup/* (status, list, get/update config, restore sqlite, restore timeseries) have no auth: routes in backup/api.rs carry no auth combinator and main.rs:3606 wires them via get_backup_routes with no admin_middleware |
| Self-signed TLS cert trust | 📋 Operational | Installed to OS trust store on first run (macOS: login.keychain, Windows: CurrentUser\Root). LAN clients must trust or accept warning |
| Rate limiting on auth routes | ✅ Active | 30 req/60s per email — clarity:backend/src-tauri/src/main.rs:133-134 |
| Login lockout after rate-limit | ✅ Active | Account locked auth_lockout_seconds (default 15 min) → HTTP 429 ACCOUNT_LOCKED. main.rs:139-202 |
| SQLite tamper detection | ✅ Active | SQLCipher v4 per-page HMAC + startup PRAGMA integrity_check fail-fast. db/mod.rs:128-141, 1233-1259 |
| Python service tamper detection | ✅ Active | Build-time SHA-256 manifest; each service dir verified before spawn. integrity/mod.rs, service_manager.rs:485-495 |
| App secrets out of binary/seed | ✅ Active | Encrypted secret-pack file (machine + binary bound); plaintext removed from users.json/configs.json (and opcuser.json deleted). Note: ADMIN_PASSWORD may be baked in as a build-time fallback. secure_store.rs |
| Auth rate limiting on API routes | 📋 Partial | SQLite API has per-user rate limiting; time-series routes do not |
RESOLVED (2026-06-02): The six
/api/admin/backup/*endpoints have no authentication.backup/api.rsdefines them with no auth combinator, andmain.rs:3606wires them into the route chain viabackup::get_backup_routes(...)with noadmin_middleware— contrast the explicitauth::admin_middlewarecalls atmain.rs:2810/2847for other admin routes. Logged in the hardening table above as a confirmed gap. Source:clarity:backend/src-tauri/src/backup/api.rs:11-314,clarity:backend/src-tauri/src/backup/mod.rs:65-71,clarity:backend/src-tauri/src/main.rs:3606.
Last updated: 2026-06-20 — verified from clarity:backend/src-tauri/src/auth.rs + main.rs + secure_store.rs + sqlite_api/db/mod.rs + integrity/ + backup/