Cloud Sync
Flamingo Sync is an optional cloud sync service that keeps your data in sync across devices. All data is encrypted client-side with AES-256-GCM before it ever leaves your device.
Architecture
Section titled “Architecture”┌───────────────────────┐ ┌──────────────────────────┐│ Electron Client │ │ Next.js Sync Server ││ │ │ ││ ┌─────────────────┐ │ │ ┌────────────────────┐ ││ │ Zustand Store │ │ │ │ API Routes │ ││ │ (local state) │ │ HTTP │ │ /init, /token, │ ││ │ │ │ │◄─────►│ │ /claim, /data, │ ││ │ ▼ │ │ │ │ /config, /key, │ ││ │ AES-256-GCM │ │ │ │ /rotate, /revoke │ ││ │ Encrypt/Decrypt │ │ │ └────────┬───────────┘ ││ │ │ │ │ │ │ ││ │ ▼ │ │ │ ┌────────▼───────────┐ ││ │ Sync Client │ │ │ │ Supabase │ ││ │ (apiFetch) │ │ │ │ - sync_data │ ││ └─────────────────┘ │ │ │ - sync_sessions │ ││ │ │ │ - sync_configs │ ││ ┌─────────────────┐ │ │ │ - sync_temp_tokens│ ││ │ SyncProvider │ │ │ │ - devices │ ││ │ (init on boot) │ │ │ │ - audit_logs │ ││ └─────────────────┘ │ │ └────────────────────┘ ││ │ │ ││ ┌─────────────────┐ │ └──────────────────────────┘│ │ useAutoSync │ ││ │ (debounced) │ ││ └─────────────────┘ │└───────────────────────┘Connection Flow
Section titled “Connection Flow”Flamingo Sync uses a device authorization grant (OAuth-like) flow:
Client Browser Server │ │ │ │ POST /api/sync/init │ │ │ ------------------------------------------> │ │ ◄── { temp_token, login_url } │ │ │ │ │ Open browser ───────────────►│ │ │ │ POST /api/sync/ │ │ │ claim │ │ │ ────────────────► │ │ │ ◄── success │ │ │ │ │ POST /api/sync/token │ │ │ (poll every 2s, max 60x) │ │ │ ───────────────────────────────────────────► │ │ ◄── { session_token, │ │ sync_config } │ │ │ │ │ PUT /api/sync/key │ │ │ ───────────────────────────────────────────► │ │ ◄── master key stored │ │ │ │ │ Sync begins ───────────────► │ │- Init — Client calls
POST /api/sync/initto get a temporary token and login URL - Authorize — Browser opens to the login URL; user authenticates
- Claim — Browser calls
POST /api/sync/claimwith the temp token - Poll — Client polls
POST /api/sync/tokenevery 2 seconds (up to 60 attempts = 2 min timeout) until the temp token is claimed - Session — Server issues a permanent session token (Bearer auth) valid for 90 days
- Key Exchange — Client generates an AES-256-GCM master key, uploads it to the server encrypted
- Sync — Data sync begins bidirectionally
Data Flow
Section titled “Data Flow”Upload
Section titled “Upload”- Data changes in a Zustand store (tabs, collections, environments, history, settings)
useAutoSynchook detects the change and debounces (2 second delay)- Serialized data is encrypted with AES-256-GCM using the master key
- Encrypted blob + nonce are sent to
PUT /api/sync/data/[type] - Server stores the blob as-is (zero-knowledge — server never sees plaintext)
Download (on connect)
Section titled “Download (on connect)”SyncProvidermounts on app boot- Validates existing session token
- Retrieves master key from server (
GET /api/sync/key) - Calls
GET /api/sync/datato download all encrypted blobs - Decrypts each blob with the master key
- Parses JSON and restores into respective Zustand stores
Data Types Synced
Section titled “Data Types Synced”| Data Type | Store | Serialized Content |
|---|---|---|
history | history-store | Array of HistoryEntry objects (max 200) |
environment | environment-store | Array of Environment objects with variables |
collection | collection-store | Array of Collection objects (nested tree) |
setting | settings-store | Settings object (theme, font size, timeout, etc.) |
Each data type can be toggled on/off individually in the Sync Settings modal.
Sync Panel (Sidebar)
Section titled “Sync Panel (Sidebar)”The Sync panel in the sidebar (cloud icon) shows:
| State | UI |
|---|---|
| Disconnected | CloudOff icon, “Not Connected”, Connect button |
| Connecting | Connecting state with spinner on button |
| Connected | Cloud icon (emerald), “Connected”, last sync time, sync toggles, Sync Now + Disconnect buttons |
| Syncing | Spinner icon, “Syncing…” |
| Error | AlertCircle icon (red), “Sync Error”, error message |
Each sync category shows:
- Label (History, Environments, Collections, Settings)
- CheckCircle icon (emerald) if enabled, “off” label if disabled
Encryption
Section titled “Encryption”| Property | Value |
|---|---|
| Algorithm | AES-256-GCM (Advanced Encryption Standard in Galois/Counter Mode) |
| Key Size | 256 bits |
| Nonce (IV) | 12 random bytes per encryption |
| Key Storage | Encrypted with session token on server |
| Key Rotation | Supported via Settings → Security |
| Zero-Knowledge | Server never sees plaintext data |
The master key is generated client-side using crypto.subtle.generateKey(). It is exported as raw bytes, hex-encoded, and stored in both:
- localStorage (
flamingo-sync-master-key) for offline/local access - Server (encrypted in
sync_sessions.encrypted_master_key) for multi-device sync
Each encryption call generates a fresh 12-byte nonce using crypto.getRandomValues(). The nonce is stored alongside the encrypted blob and required for decryption.
Auto-Sync
Section titled “Auto-Sync”Data is synced automatically when changes occur:
- Trigger: Any store mutation (history add, environment variable change, collection update, settings change)
- Debounce: 2 seconds after the last change before upload
- Connection Check: Skips upload if not connected
- Silent Failure: Auto-sync errors are silently caught (no user disruption)
Use the Sync Now button in the Sync panel for immediate sync.
Session Management
Section titled “Session Management”| Event | Behavior |
|---|---|
| Connect | Opens browser for OAuth, polls for session, sets up encryption |
| Disconnect | Calls POST /api/sync/revoke, clears session from localStorage |
| Token Rotation | Issues new session token, old token invalidated |
| Session Expiry | Tokens expire after 90 days; re-authentication required |
| App Boot | SyncProvider validates session, fetches master key, downloads data |
Device Management
Section titled “Device Management”Settings → Security lets you:
| Action | API | Description |
|---|---|---|
| View devices | GET /api/devices | List all connected devices with type, status, last seen |
| Revoke device | DELETE /api/devices/[id] | Remove a device from sync |
| Rotate key | POST /api/sync/rotate | Issue new session token |
| Disconnect | POST /api/sync/revoke | Revoke current session entirely |
API Reference
Section titled “API Reference”All sync API endpoints are prefixed under the configured server URL.
Authentication
Section titled “Authentication”Most endpoints require a Bearer token obtained from the connection flow:
Authorization: Bearer <session_token>Endpoints
Section titled “Endpoints”| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/sync/init | No | Start device authorization, get temp token |
| POST | /api/sync/claim | Cookie/Bearer | Claim temp token (browser) |
| POST | /api/sync/token | No | Poll for session token after claim |
| GET | /api/sync/config | Bearer | Get sync preferences |
| PUT | /api/sync/config | Bearer | Update sync preferences |
| PUT | /api/sync/key | Bearer | Store master encryption key |
| GET | /api/sync/key | Bearer | Retrieve master encryption key |
| PUT | /api/sync/data/[type] | Bearer | Upload encrypted data blob |
| GET | /api/sync/data/[type] | Bearer | Download encrypted data blob |
| GET | /api/sync/data | Bearer | Download all encrypted data blobs |
| POST | /api/sync/rotate | Bearer | Rotate session token |
| POST | /api/sync/revoke | Bearer | Revoke current session |
| GET | /api/devices | Bearer | List connected devices |
| DELETE | /api/devices/[id] | Bearer | Revoke a device |
Sync Config Schema
Section titled “Sync Config Schema”{ "sync_history": true, "sync_environments": true, "sync_secrets": true, "sync_collections": true, "sync_settings": true}Security Considerations
Section titled “Security Considerations”- Master key compromise — If someone gains access to your master key, they can decrypt all synced data. Rotate your key immediately from Settings if you suspect compromise.
- Session token theft — Tokens are stored in localStorage. They grant access to sync data (but not decryption without the master key). Disconnect and reconnect to invalidate.
- Server compromise — Even if the sync server is fully compromised, encrypted blobs cannot be decrypted without the master key (zero-knowledge architecture).
- Local access — All data is also stored in localStorage. Anyone with physical access to your machine can read it. Flamingo does not encrypt local storage.
Troubleshooting
Section titled “Troubleshooting”| Problem | Likely Cause | Solution |
|---|---|---|
| Connection times out | Server unreachable or wrong URL | Check server URL, verify server is running |
| ”Session expired” | Token older than 90 days | Reconnect to get a fresh token |
| Sync fails silently | Auto-sync error (debounced) | Use “Sync Now” button to see errors |
| ”Master key not found” | New device, no key on server | Reconnect — new key will be generated |
| Data conflicts | Changed same data on two devices | Last write wins (no conflict resolution) |