Security Architecture
Celistra is zero-trust between the browser and the daemon: the daemon assumes every inbound request is hostile until proven otherwise.
1. Machine Secret
Each celistrad generates a 256-bit hex secret on first run (stored in ~/.celistra_auth.json, chmod 600). Every authenticated request carries Authorization: Bearer <secret>.
The secret is never sent to Firestore. Rotating it requires re-pairing.
2. Pairing (Challenge–Response)
Dashboards obtain the secret via a transient handshake:
- User clicks Pair new machine in the tray. Daemon mints an 8-digit one-time token (TTL 120s).
- Tray opens
https://app.celistra.dev/?t=<token>in the default browser. - Dashboard POSTs
{ firebaseIdToken, token }toPOST /v1/auth. - Daemon verifies the Firebase ID token against the configured UID, checks the challenge, and — on success — returns the Bearer secret + tunnel URL.
- Token is burned; a second attempt returns 401.
If the Firebase UID doesn't match the daemon's expected owner UID, the request is rejected regardless of the token.
3. Transport
- REST / SSE:
Authorization: Bearer. - WebSocket: browsers can't set headers on upgrade, so the secret goes in the query string (
?secret=<s>). The daemon accepts this only on/v1/terminal. - CORS: allowed origins are the app domain and
null/file://for the tray's local dashboard. Credentials are not required. - Private Network Access: daemon replies with
Access-Control-Allow-Private-Network: trueso Chrome lets the public app call into127.0.0.1.
4. Command Policy
isSafeCommand in main.go is an allowlist-plus-denylist:
- Blocks destructive patterns (
rm -rf /,sudo,mkfs,dd, shell redirect-to-device, fork bombs). - Returns 403 with
Security Policy Violation. - Policy is per-daemon and hardcoded — no remote override.
5. Local-Only Dashboard
GET / on 127.0.0.1:33120 serves dashboard_html.go without the bearer check because it runs on loopback and embeds the secret into its own <script> block at render time. It is not reachable through the SSH tunnel: the dashboard handler rejects any request whose Host is not 127.0.0.1 / localhost.
6. Auto-Restart Safety
autoRestart: true is gated by the restart supervisor:
- Max 5 restarts in a 60-second window.
- On breach, the agent is terminated with exit reason
restart_limitand written to history. - Prevents fork-bomb-on-exit scenarios.
7. Secret Hygiene
- Never call
saveConfigon a blankConfig{}— alwaysloadConfig()first, mutate, then save. Overwriting loses the machine secret and bricks every paired dashboard. - Factory Reset in the dashboard clears only the browser's local state (Zustand persist). The daemon's secret is untouched; re-pair to reconnect.
- RevenueCat App User ID = Firebase UID, so anonymous purchases can't carry Pro entitlements across accounts.