Skip to content

[Bug]: Desktop overwrites an undecryptable connection-catalog.json with an empty catalog, permanently deleting saved environments #23470

Open

Description

@rbansal42

Before submitting

Area

apps/desktop

Steps to reproduce

  1. macOS, T3 Code Nightly with several saved environments (SSH targets and remote profiles).
  2. Have connection-catalog.json encrypted under a safe-storage identity that differs from the one the running build uses. This happened naturally across nightlies: 20261020 used T3 Code (Nightly) Safe Storage, and 20261021 (the [Bug]: Mac Nightly: projects and chats appear missing after this morning's update #22216 fix) returned to t3code Safe Storage. Any user who re-saved the catalog on 20261020 now holds a file under the Nightly key.
  3. Update to the next build and open the desktop.

Expected behavior

If the catalog cannot be decrypted, the desktop leaves the file untouched. It tries other known identities (t3code, T3 Code (Nightly), T3 Code (Alpha), T3 Code (Beta)) and then re-encrypts the file under the current identity. If no identity works, it shows a recoverable error.

Actual behavior

desktop.connectionCatalogStore.get fails with DesktopConnectionCatalogStoreProtectionError (decrypt-catalog). The renderer continues with an empty catalog, and its next desktop:set-connection-catalog call writes {"targets":[],"profiles":[],"credentials":[],...} over the file. The original catalog, including credentials and DPoP tokens, is gone. Remote environments and their threads disappear from the desktop, while the web client still shows them.

In 20261021 the store's get has no previous-identity fallback (the decryptWithPreviousName path seen in 20261020 traces is gone), and set writes unconditionally.

Suggested fix

  1. Never let set replace a catalog that the last get failed to decrypt. Keep the unreadable file (or move it to connection-catalog.json.undecryptable-<ts>) until the user resolves it.
  2. Restore a fallback in get that tries every historical safe-storage identity and migrates on success.
  3. Write a .bak of the previous document before each write (also helps connection-catalog.json is written non-atomically every ~3s, and a corrupt document bricks the app permanently #4750).

Impact

Silent, permanent loss of all saved connections and their credentials after a routine update.

Version or commit

0.1.2-nightly.20261021.3499, macOS arm64.

Logs or stack traces

desktop.connectionCatalogStore.get  Failure  DesktopConnectionCatalogStoreProtectionError: decrypt-catalog
desktop.ipc.connectionCatalog.set    Success  (catalog shrinks from ~9 KB to 212 bytes)

Activity

  1. rbansal42 commented on Oct 22, 2026

    @rbansal42
    Author

    Correction to the failure path above, after reading the source: on the Mac where the catalog was wiped, desktop.connectionCatalogStore.get succeeded at startup and set followed 36 ms later. The empty write comes from the renderer's corrupt-catalog recovery in makeCatalogStore (apps/web/src/connection/storage.ts), which writes EMPTY_CONNECTION_CATALOG_DOCUMENT when the returned catalog does not decode. The desktop backend has no quarantine, so nothing is kept. On the second Mac, get failed with decrypt-catalog and nothing was written.

    Proposed fix: #23488 keeps the existing file as connection-catalog.json.unreadable-<ms> before the first write, if it does not decrypt and decode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions