Skip to content

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.

┌───────────────────────┐ ┌──────────────────────────┐
│ 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) │ │
│ └─────────────────┘ │
└───────────────────────┘

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 ───────────────► │ │
  1. Init — Client calls POST /api/sync/init to get a temporary token and login URL
  2. Authorize — Browser opens to the login URL; user authenticates
  3. Claim — Browser calls POST /api/sync/claim with the temp token
  4. Poll — Client polls POST /api/sync/token every 2 seconds (up to 60 attempts = 2 min timeout) until the temp token is claimed
  5. Session — Server issues a permanent session token (Bearer auth) valid for 90 days
  6. Key Exchange — Client generates an AES-256-GCM master key, uploads it to the server encrypted
  7. Sync — Data sync begins bidirectionally
  1. Data changes in a Zustand store (tabs, collections, environments, history, settings)
  2. useAutoSync hook detects the change and debounces (2 second delay)
  3. Serialized data is encrypted with AES-256-GCM using the master key
  4. Encrypted blob + nonce are sent to PUT /api/sync/data/[type]
  5. Server stores the blob as-is (zero-knowledge — server never sees plaintext)
  1. SyncProvider mounts on app boot
  2. Validates existing session token
  3. Retrieves master key from server (GET /api/sync/key)
  4. Calls GET /api/sync/data to download all encrypted blobs
  5. Decrypts each blob with the master key
  6. Parses JSON and restores into respective Zustand stores
Data TypeStoreSerialized Content
historyhistory-storeArray of HistoryEntry objects (max 200)
environmentenvironment-storeArray of Environment objects with variables
collectioncollection-storeArray of Collection objects (nested tree)
settingsettings-storeSettings object (theme, font size, timeout, etc.)

Each data type can be toggled on/off individually in the Sync Settings modal.

The Sync panel in the sidebar (cloud icon) shows:

StateUI
DisconnectedCloudOff icon, “Not Connected”, Connect button
ConnectingConnecting state with spinner on button
ConnectedCloud icon (emerald), “Connected”, last sync time, sync toggles, Sync Now + Disconnect buttons
SyncingSpinner icon, “Syncing…”
ErrorAlertCircle icon (red), “Sync Error”, error message

Each sync category shows:

  • Label (History, Environments, Collections, Settings)
  • CheckCircle icon (emerald) if enabled, “off” label if disabled
PropertyValue
AlgorithmAES-256-GCM (Advanced Encryption Standard in Galois/Counter Mode)
Key Size256 bits
Nonce (IV)12 random bytes per encryption
Key StorageEncrypted with session token on server
Key RotationSupported via Settings → Security
Zero-KnowledgeServer 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.

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.

EventBehavior
ConnectOpens browser for OAuth, polls for session, sets up encryption
DisconnectCalls POST /api/sync/revoke, clears session from localStorage
Token RotationIssues new session token, old token invalidated
Session ExpiryTokens expire after 90 days; re-authentication required
App BootSyncProvider validates session, fetches master key, downloads data

Settings → Security lets you:

ActionAPIDescription
View devicesGET /api/devicesList all connected devices with type, status, last seen
Revoke deviceDELETE /api/devices/[id]Remove a device from sync
Rotate keyPOST /api/sync/rotateIssue new session token
DisconnectPOST /api/sync/revokeRevoke current session entirely

All sync API endpoints are prefixed under the configured server URL.

Most endpoints require a Bearer token obtained from the connection flow:

Authorization: Bearer <session_token>
MethodPathAuthDescription
POST/api/sync/initNoStart device authorization, get temp token
POST/api/sync/claimCookie/BearerClaim temp token (browser)
POST/api/sync/tokenNoPoll for session token after claim
GET/api/sync/configBearerGet sync preferences
PUT/api/sync/configBearerUpdate sync preferences
PUT/api/sync/keyBearerStore master encryption key
GET/api/sync/keyBearerRetrieve master encryption key
PUT/api/sync/data/[type]BearerUpload encrypted data blob
GET/api/sync/data/[type]BearerDownload encrypted data blob
GET/api/sync/dataBearerDownload all encrypted data blobs
POST/api/sync/rotateBearerRotate session token
POST/api/sync/revokeBearerRevoke current session
GET/api/devicesBearerList connected devices
DELETE/api/devices/[id]BearerRevoke a device
{
"sync_history": true,
"sync_environments": true,
"sync_secrets": true,
"sync_collections": true,
"sync_settings": true
}
  • 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.
ProblemLikely CauseSolution
Connection times outServer unreachable or wrong URLCheck server URL, verify server is running
”Session expired”Token older than 90 daysReconnect to get a fresh token
Sync fails silentlyAuto-sync error (debounced)Use “Sync Now” button to see errors
”Master key not found”New device, no key on serverReconnect — new key will be generated
Data conflictsChanged same data on two devicesLast write wins (no conflict resolution)