# Files changed · PR \#20152 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/20152/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[fix\(desktop\): recover saved connections across Windows profile keys](https://git.cafe/pingdotgg/t3code/pull/20152.md)

Version: 4

Head commit: 24c644b5c7e9692a7674d2f0cd5e176b2d97b67d

Base commit: 1451e7cb23034d29774338608368a15d8845df36

Comparison base: 1451e7cb23034d29774338608368a15d8845df36

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts

Status: modified; +312; −2

```diff
@@ -1,8 +1,13 @@
+// @effect-diagnostics nodeBuiltinImport:off - native-format fixtures exercise Chromium AES-GCM interoperability.
+import * as NodeCrypto from "node:crypto";
+import { beforeEach, vi } from "vite-plus/test";
 import * as NodeServices from "@effect/platform-node/NodeServices";
 import { assert, describe, it } from "@effect/vitest";
 import { ConnectionCatalogDocument } from "@t3tools/client-runtime/platform";
 import { EnvironmentId, type PersistedSavedEnvironmentRecord } from "@t3tools/contracts";
+import * as Deferred from "effect/Deferred";
 import * as Effect from "effect/Effect";
+import * as Fiber from "effect/Fiber";
 import * as Base64 from "effect/encoding/Base64";
 import * as FileSystem from "effect/FileSystem";
 import * as Path from "effect/Path";
@@ -11,6 +16,7 @@
 import * as PlatformError from "effect/PlatformError";
 import * as Ref from "effect/Ref";
 import * as Schema from "effect/Schema";
+import * as TestClock from "effect/testing/TestClock";
 
 import * as ElectronSafeStorage from "../electron/ElectronSafeStorage.ts";
 import * as DesktopSavedEnvironments from "../settings/DesktopSavedEnvironments.ts";
@@ -18,6 +24,28 @@
 import * as DesktopConnectionCatalogStore from "./DesktopConnectionCatalogStore.ts";
 import * as DesktopEnvironment from "./DesktopEnvironment.ts";
 
+const { legacyKeys, keyReads } = vi.hoisted(() => ({
+  legacyKeys: new Map<string, Buffer>(),
+  keyReads: vi.fn<(path: string) => Effect.Effect<void>>(),
+}));
+vi.mock("../preview/BrowserImport/ChromiumKeys.ts", async (original) => {
+  const actual = await original<typeof import("../preview/BrowserImport/ChromiumKeys.ts")>();
+  return {
+    ...actual,
+    readWindowsKey: (path: string) =>
+      Effect.gen(function* () {
+        yield* keyReads(path);
+        const key = legacyKeys.get(path);
+        return yield* key
+          ? Effect.succeed(key)
+          : Effect.fail(new actual.ChromiumKeyError({ reason: "readFailed" }));
+      }),
+  };
+});
+beforeEach(() => {
+  legacyKeys.clear();
+  keyReads.mockReset().mockReturnValue(Effect.void);
+});
 const textDecoder = new TextDecoder();
 const textEncoder = new TextEncoder();
 const decodeConnectionCatalog = Schema.decodeEffect(
@@ -55,11 +83,13 @@
   encryptionAvailable = true,
   failDecrypt: Ref.Ref<boolean> | null = null,
   fileSystemLayer: Layer.Layer<FileSystem.FileSystem> = NodeServices.layer,
+  platform: NodeJS.Platform = "darwin",
+  storage = layerSafeStorageFor(encryptionAvailable, failDecrypt),
 ) {
   const layerEnvironment = DesktopEnvironment.layer({
     dirname: "/repo/apps/desktop/src",
     homeDirectory: baseDir,
-    platform: "darwin",
+    platform,
     processArch: "arm64",
     appVersion: "1.2.3",
     appPath: "/repo",
@@ -71,7 +101,7 @@
       Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({ T3CODE_HOME: baseDir })),
     ),
   );
+  const layerSafeStorage = storage;
-  const layerSafeStorage = layerSafeStorageFor(encryptionAvailable, failDecrypt);
   const layerDependencies = Layer.mergeAll(
     layerEnvironment,
     layerSafeStorage,
@@ -441,3 +471,283 @@
     }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
   );
 });
+
+const seal = (value: string, key: Buffer) => {
+  const nonce = Buffer.alloc(12, 4);
+  const cipher = NodeCrypto.createCipheriv("aes-256-gcm", key, nonce);
+  return Buffer.concat([
+    Buffer.from("v10"),
+    nonce,
+    cipher.update(value, "utf8"),
+    cipher.final(),
+    cipher.getAuthTag(),
+  ]);
+};
+const open = (bytes: Uint8Array, key: Buffer) => {
+  const data = Buffer.from(bytes);
+  assert.equal(data.subarray(0, 3).toString("ascii"), "v10");
+  const decipher = NodeCrypto.createDecipheriv("aes-256-gcm", key, data.subarray(3, 15));
+  decipher.setAuthTag(data.subarray(-16));
+  return Buffer.concat([decipher.update(data.subarray(15, -16)), decipher.final()]).toString(
+    "utf8",
+  );
+};
+
+const stableKey = Buffer.alloc(32, 1);
+const alphaKey = Buffer.alloc(32, 2);
+const currentKey = Buffer.alloc(32, 3);
+const encryptedDocument = (encrypted: Uint8Array) =>
+  JSON.stringify({ version: 1, encryptedCatalog: Base64.encode(encrypted) });
+
+const cryptoStore = (platform: NodeJS.Platform = "win32") =>
+  Effect.gen(function* () {
+    const fs = yield* FileSystem.FileSystem;
+    const path = yield* Path.Path;
+    const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-catalog-key-" });
+    const profiles = [];
+    for (const [name, key] of [
+      ["t3code", stableKey],
+      ["T3 Code (Alpha)", alphaKey],
+      ["t3code-v2", currentKey],
+    ] as const) {
+      const statePath = path.join(root, "AppData", "Roaming", name, "Local State");
+      const state = JSON.stringify({ os_crypt: { encrypted_key: `wrapped-${name}` } });
+      const tokenPath = path.join(path.dirname(statePath), "clerk-tokens.json");
+      const token = seal(`${name} token`, key);
+      yield* fs.makeDirectory(path.dirname(statePath), { recursive: true });
+      yield* fs.writeFileString(statePath, state);
+      yield* fs.writeFile(tokenPath, token);
+      legacyKeys.set(statePath, key);
+      profiles.push({ name, key, statePath, state, tokenPath, token });
+    }
+    const nativeDecrypt = vi.fn((value: Uint8Array) =>
+      value.length < 31
+        ? Effect.die(new Error("Windows safeStorage aborts on truncated ciphertext"))
+        : Effect.try({
+            try: () => open(value, currentKey),
+            catch: (cause) => new ElectronSafeStorage.ElectronSafeStorageDecryptError({ cause }),
+          }),
+    );
+    const nativeEncrypt = vi.fn((value: string) => Effect.succeed(seal(value, currentKey)));
+    const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore.pipe(
+      Effect.provide(
+        layerFor(
+          root,
+          true,
+          null,
+          NodeServices.layer,
+          platform,
+          Layer.succeed(ElectronSafeStorage.ElectronSafeStorage, {
+            isEncryptionAvailable: Effect.succeed(true),
+            encryptString: nativeEncrypt,
+            decryptString: nativeDecrypt,
+            selectedStorageBackend: Effect.succeedNone,
+          }),
+        ),
+      ),
+    );
+    const catalogPath = path.join(root, "userdata", "connection-catalog.json");
+    yield* fs.makeDirectory(path.dirname(catalogPath), { recursive: true });
+    const readCatalog = fs.readFileString(catalogPath);
+    return {
+      store,
+      profiles,
+      nativeDecrypt,
+      nativeEncrypt,
+      readCatalog,
+      replaceCatalog: (document: string) => fs.writeFileString(catalogPath, document),
+      readWithKey: (key: Buffer) =>
+        readCatalog.pipe(
+          Effect.map((document) =>
+            open(Buffer.from(JSON.parse(document).encryptedCatalog, "base64"), key),
+          ),
+        ),
+    };
+  });
+
+describe("Windows catalog key recovery", () => {
+  it.effect.each([
+    { owner: "t3code", key: stableKey },
+    { owner: "T3 Code (Alpha)", key: alphaKey },
+  ])(
+    "recovers $owner after native decryption fails without changing profile keys or tokens",
+    ({ owner, key }) =>
+      Effect.gen(function* () {
+        const fs = yield* FileSystem.FileSystem;
+        const fixture = yield* cryptoStore();
+        // Alpha must remain usable when the stable profile key cannot be read.
+        if (owner === "T3 Code (Alpha)") {
+          const stable = fixture.profiles.find((profile) => profile.name === "t3code")!;
+          legacyKeys.delete(stable.statePath);
+        }
+        keyReads.mockImplementation(() =>
+          Effect.sync(() => assert.isAbove(fixture.nativeDecrypt.mock.calls.length, 0)),
+        );
+        const initial = encryptedDocument(seal("saved connections", key));
+        yield* fixture.replaceCatalog(initial);
+        assert.deepStrictEqual(yield* fixture.store.get, Option.some("saved connections"));
+        assert.equal(yield* fixture.readCatalog, initial);
+        assert.isTrue(yield* fixture.store.set("updated connections"));
+        assert.equal(yield* fixture.readWithKey(key), "updated connections");
+        for (const profile of fixture.profiles) {
+          assert.equal(yield* fs.readFileString(profile.statePath), profile.state);
+          assert.deepStrictEqual(
+            yield* fs.readFile(profile.tokenPath),
+            new Uint8Array(profile.token),
+          );
+          assert.equal(
+            open(yield* fs.readFile(profile.tokenPath), profile.key),
+            `${profile.name} token`,
+          );
+        }
+      }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
+  );
+
+  it.effect("bounds stalled key lookups, continues recovery, and caches timed-out candidates", () =>
+    Effect.gen(function* () {
+      const fixture = yield* cryptoStore();
+      const started = yield* Deferred.make<void>();
+      const interrupted = yield* Deferred.make<void>();
+      keyReads.mockImplementation((path) =>
+        path.includes("T3 Code (Alpha)")
+          ? Effect.void
+          : Deferred.succeed(started, undefined).pipe(
+              Effect.andThen(Effect.never),
+              Effect.onInterrupt(() => Deferred.succeed(interrupted, undefined)),
+            ),
+      );
+      yield* fixture.replaceCatalog(encryptedDocument(seal("Alpha connections", alphaKey)));
+      const recovery = yield* fixture.store.get.pipe(Effect.forkChild);
+      yield* Deferred.await(started);
+      yield* TestClock.adjust("10 seconds");
+      // Assert before joining so a missing deadline fails without waiting on the stalled fiber.
+      assert.isTrue(yield* Deferred.isDone(interrupted));
+      assert.deepStrictEqual(yield* Fiber.join(recovery), Option.some("Alpha connections"));
+      assert.isTrue(yield* fixture.store.set("updated Alpha connections"));
+      assert.equal(yield* fixture.readWithKey(alphaKey), "updated Alpha connections");
+      assert.equal(keyReads.mock.calls.length, 2);
+
+      const allStalled = yield* cryptoStore();
+      const candidates = yield* Effect.forEach(allStalled.profiles.slice(0, 2), ({ statePath }) =>
+        Effect.gen(function* () {
+          return {
+            statePath,
+            started: yield* Deferred.make<void>(),
+            interrupted: yield* Deferred.make<void>(),
+          };
+        }),
+      );
+      keyReads.mockImplementation((path) => {
+        const candidate = candidates.find(({ statePath }) => path === statePath)!;
+        return Deferred.succeed(candidate.started, undefined).pipe(
+          Effect.andThen(Effect.never),
+          Effect.onInterrupt(() => Deferred.succeed(candidate.interrupted, undefined)),
+        );
+      });
+      const nativeError = new ElectronSafeStorage.ElectronSafeStorageDecryptError({
+        cause: new Error("native catalog key does not match"),
+      });
+      allStalled.nativeDecrypt.mockReturnValue(Effect.fail(nativeError));
+      const initial = encryptedDocument(seal("unavailable connections", stableKey));
+      yield* allStalled.replaceCatalog(initial);
+      const failed = yield* allStalled.store.get.pipe(Effect.flip, Effect.forkChild);
+      for (const candidate of candidates) {
+        yield* Deferred.await(candidate.started);
+        yield* TestClock.adjust("10 seconds");
+        assert.isTrue(yield* Deferred.isDone(candidate.interrupted));
+      }
+      assert.strictEqual((yield* Fiber.join(failed)).cause, nativeError);
+      assert.strictEqual((yield* allStalled.store.get.pipe(Effect.flip)).cause, nativeError);
+      assert.strictEqual(
+        (yield* allStalled.store.set("must not overwrite").pipe(Effect.flip)).cause,
+        nativeError,
+      );
+      assert.equal(yield* allStalled.readCatalog, initial);
+      assert.equal(keyReads.mock.calls.length, 4);
+    }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
+  );
+
+  it.effect("authenticates writes before any read and follows a replacement catalog key", () =>
+    Effect.gen(function* () {
+      const fixture = yield* cryptoStore();
+      yield* fixture.replaceCatalog(encryptedDocument(seal("stable connections", stableKey)));
+      assert.isTrue(yield* fixture.store.set("first write"));
+      assert.equal(yield* fixture.readWithKey(stableKey), "first write");
+      yield* fixture.replaceCatalog(encryptedDocument(seal("Alpha connections", alphaKey)));
+      assert.isTrue(yield* fixture.store.set("after replacement"));
+      assert.equal(yield* fixture.readWithKey(alphaKey), "after replacement");
+      assert.deepStrictEqual(yield* fixture.store.get, Option.some("after replacement"));
+
+      yield* fixture.replaceCatalog(encryptedDocument(seal("native connections", currentKey)));
+      assert.isTrue(yield* fixture.store.set("native update"));
+      assert.equal(yield* fixture.readWithKey(currentKey), "native update");
+      assert.deepStrictEqual(yield* fixture.store.get, Option.some("native update"));
+      assert.equal(fixture.nativeEncrypt.mock.calls.length, 1);
+    }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
+  );
+
+  it.effect(
+    "rejects unreadable catalogs and refuses to overwrite them after recovering a key",
+    () =>
+      Effect.gen(function* () {
+        const fixture = yield* cryptoStore();
+        yield* fixture.replaceCatalog(encryptedDocument(seal("saved connections", stableKey)));
+        assert.deepStrictEqual(yield* fixture.store.get, Option.some("saved connections"));
+        const tampered = seal("saved connections", stableKey);
+        tampered.writeUInt8(tampered.readUInt8(tampered.length - 1) ^ 1, tampered.length - 1);
+        const malformedHeader = seal("saved connections", stableKey);
+        malformedHeader.writeUInt8(malformedHeader.readUInt8(0) | 0x80, 0);
+        for (const [document, errorTag] of [
+          ["{not-json", "DesktopConnectionCatalogStoreDocumentDecodeError"],
+          [
+            JSON.stringify({ version: 1, encryptedCatalog: "%%%" }),
+            "DesktopConnectionCatalogStoreDecodeError",
+          ],
+          ...[
+            Buffer.alloc(0),
+            Buffer.from("v10short"),
+            malformedHeader,
+            seal("unknown key", Buffer.alloc(32, 9)),
+            tampered,
+          ].map(
+            (encrypted) =>
+              [
+                encryptedDocument(encrypted),
+                "DesktopConnectionCatalogStoreProtectionError",
+              ] as const,
+          ),
+        ] as const) {
+          yield* fixture.replaceCatalog(document);
+          assert.equal((yield* fixture.store.get.pipe(Effect.flip))._tag, errorTag);
+          assert.equal(
+            (yield* fixture.store.set("must not overwrite").pipe(Effect.flip))._tag,
+            errorTag,
+          );
+          assert.equal(yield* fixture.readCatalog, document);
+        }
+        assert.equal(fixture.nativeEncrypt.mock.calls.length, 0);
+      }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
+  );
+
+  it.effect("leaves native success and non-Windows failure handling unchanged", () =>
+    Effect.gen(function* () {
+      const windows = yield* cryptoStore();
+      assert.isTrue(yield* windows.store.set("new native catalog"));
+      assert.equal(yield* windows.readWithKey(currentKey), "new native catalog");
+      assert.deepStrictEqual(yield* windows.store.get, Option.some("new native catalog"));
+      assert.equal(keyReads.mock.calls.length, 0);
+
+      const nonWindows = yield* cryptoStore("darwin");
+      const initial = encryptedDocument(seal("legacy connections", stableKey));
+      yield* nonWindows.replaceCatalog(initial);
+      assert.equal(
+        (yield* nonWindows.store.get.pipe(Effect.flip))._tag,
+        "DesktopConnectionCatalogStoreProtectionError",
+      );
+      assert.equal(yield* nonWindows.readCatalog, initial);
+      assert.isTrue(yield* nonWindows.store.set("native replacement"));
+      assert.equal(yield* nonWindows.readWithKey(currentKey), "native replacement");
+      assert.equal(keyReads.mock.calls.length, 0);
+    }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
+  );
+});
```

## apps/desktop/src/app/DesktopConnectionCatalogStore.ts

Status: modified; +39; −15

```diff
@@ -25,6 +25,7 @@
 import * as ElectronSafeStorage from "../electron/ElectronSafeStorage.ts";
 import * as DesktopSavedEnvironments from "../settings/DesktopSavedEnvironments.ts";
 import * as DesktopEnvironment from "./DesktopEnvironment.ts";
+import * as DesktopWindowsCatalogKey from "./DesktopWindowsCatalogKey.ts";
 
 const EncryptedConnectionCatalogDocument = Schema.Struct({
   version: Schema.Literal(1),
@@ -161,7 +162,11 @@
       catalog: string,
     ) => Effect.Effect<
       boolean,
+      | DesktopConnectionCatalogStoreWriteError
+      | DesktopConnectionCatalogStoreProtectionError
+      | DesktopConnectionCatalogStoreReadError
+      | DesktopConnectionCatalogStoreDocumentDecodeError
+      | DesktopConnectionCatalogStoreDecodeError
-      DesktopConnectionCatalogStoreWriteError | DesktopConnectionCatalogStoreProtectionError
     >;
     readonly clear: Effect.Effect<void>;
   }
@@ -385,6 +390,26 @@
   const crypto = yield* Crypto.Crypto;
   const savedEnvironments = yield* DesktopSavedEnvironments.DesktopSavedEnvironments;
   const catalogPath = path.join(environment.stateDir, "connection-catalog.json");
+  const decryptWindowsCatalog =
+    environment.platform === "win32" && !environment.isDevelopment
+      ? yield* DesktopWindowsCatalogKey.make(environment.appDataDirectory)
+      : undefined;
+  const decryptCatalog = (encrypted: Uint8Array) =>
+    (decryptWindowsCatalog === undefined
+      ? safeStorage
+          .decryptString(encrypted)
+          .pipe(Effect.map((catalog) => ({ catalog, encrypt: safeStorage.encryptString })))
+      : decryptWindowsCatalog(encrypted)
+    ).pipe(
+      Effect.mapError(
+        (cause) =>
+          new DesktopConnectionCatalogStoreProtectionError({
+            operation: "decrypt-catalog",
+            catalogPath,
+            cause,
+          }),
+      ),
+    );
   const encryptionAvailable = safeStorage.isEncryptionAvailable.pipe(
     Effect.mapError(
       (cause) =>
@@ -399,8 +424,18 @@
   const writeCatalog = Effect.fn("desktop.connectionCatalogStore.writeCatalog")(function* (
     catalog: string,
   ) {
+    // V1 shares this file and must retain access after V2 writes, even after external replacement.
+    const existing =
+      decryptWindowsCatalog === undefined
+        ? Option.none<EncryptedConnectionCatalogDocument>()
+        : yield* readDocument(fileSystem, catalogPath);
+    const encrypt = Option.isSome(existing)
+      ? (yield* decryptCatalog(
+          yield* decodeSecretBytes(catalogPath, existing.value.encryptedCatalog),
+        )).encrypt
+      : safeStorage.encryptString;
     const encryptedCatalog = Base64.encode(
+      yield* encrypt(catalog).pipe(
-      yield* safeStorage.encryptString(catalog).pipe(
         Effect.mapError(
           (cause) =>
             new DesktopConnectionCatalogStoreProtectionError({
@@ -481,20 +516,9 @@
         return Option.none<string>();
       }
       const decrypted = yield* decodeSecretBytes(catalogPath, document.value.encryptedCatalog).pipe(
+        Effect.flatMap(decryptCatalog),
-        Effect.flatMap((encryptedCatalog) =>
-          safeStorage.decryptString(encryptedCatalog).pipe(
-            Effect.mapError(
-              (cause) =>
-                new DesktopConnectionCatalogStoreProtectionError({
-                  operation: "decrypt-catalog",
-                  catalogPath,
-                  cause,
-                }),
-            ),
-          ),
-        ),
       );
+      return Option.some(decrypted.catalog);
-      return Option.some(decrypted);
     }).pipe(Effect.withSpan("desktop.connectionCatalogStore.get")),
     set: Effect.fn("desktop.connectionCatalogStore.set")(function* (catalog) {
       if (!(yield* encryptionAvailable)) {
```

## apps/desktop/src/app/DesktopWindowsCatalogKey.ts

Status: added; +92; −0

```diff
@@ -1,0 +1,92 @@
+// @effect-diagnostics nodeBuiltinImport:off - Effect has no AES-GCM primitive for Windows safeStorage compatibility.
+import * as NodeCrypto from "node:crypto";
+import * as Effect from "effect/Effect";
+import * as FileSystem from "effect/FileSystem";
+import * as Option from "effect/Option";
+import * as Path from "effect/Path";
+import { ChildProcessSpawner } from "effect/process";
+
+import * as ElectronSafeStorage from "../electron/ElectronSafeStorage.ts";
+import { readWindowsKey } from "../preview/BrowserImport/ChromiumKeys.ts";
+
+const PREFIX = Buffer.from("v10");
+const NONCE_LENGTH = 12;
+const TAG_LENGTH = 16;
+const CIPHERTEXT_OFFSET = PREFIX.length + NONCE_LENGTH;
+
+export const make = Effect.fn("desktop.windowsCatalogKey.make")(function* (
+  appDataDirectory: string,
+) {
+  const safeStorage = yield* ElectronSafeStorage.ElectronSafeStorage;
+  const path = yield* Path.Path;
+  const context = yield* Effect.context<
+    FileSystem.FileSystem | ChildProcessSpawner.ChildProcessSpawner
+  >();
+  // Failed lookups are cached too; restoring a profile key requires an app restart.
+  const keys = yield* Effect.cached(
+    Effect.forEach(["t3code", "T3 Code (Alpha)"], (profile) =>
+      readWindowsKey(path.join(appDataDirectory, profile, "Local State")).pipe(
+        Effect.timeout("10 seconds"),
+        Effect.option,
+      ),
+    ).pipe(Effect.provide(context)),
+  );
+  return Effect.fn("desktop.windowsCatalogKey.decrypt")(function* (encrypted: Uint8Array) {
+    const bytes = Buffer.from(encrypted);
+    // Native Windows safeStorage can abort on truncated v10 payloads.
+    if (
+      bytes.length < CIPHERTEXT_OFFSET + TAG_LENGTH ||
+      !bytes.subarray(0, PREFIX.length).equals(PREFIX)
+    ) {
+      return yield* new ElectronSafeStorage.ElectronSafeStorageDecryptError({
+        cause: new Error("Invalid Windows catalog ciphertext framing."),
+      });
+    }
+    return yield* safeStorage.decryptString(encrypted).pipe(
+      Effect.map((catalog) => ({ catalog, encrypt: safeStorage.encryptString })),
+      Effect.catch((error) =>
+        Effect.gen(function* () {
+          for (const key of yield* keys) {
+            if (Option.isNone(key)) continue;
+            const catalog = yield* Effect.try({
+              try: () => {
+                const decipher = NodeCrypto.createDecipheriv(
+                  "aes-256-gcm",
+                  key.value,
+                  bytes.subarray(PREFIX.length, CIPHERTEXT_OFFSET),
+                );
+                decipher.setAuthTag(bytes.subarray(-TAG_LENGTH));
+                return Buffer.concat([
+                  decipher.update(bytes.subarray(CIPHERTEXT_OFFSET, -TAG_LENGTH)),
+                  decipher.final(),
+                ]).toString("utf8");
+              },
+              catch: () => error,
+            }).pipe(Effect.option);
+            if (Option.isNone(catalog)) continue;
+            return {
+              catalog: catalog.value,
+              encrypt: (value: string) =>
+                Effect.try({
+                  try: () => {
+                    const nonce = NodeCrypto.randomBytes(NONCE_LENGTH);
+                    const cipher = NodeCrypto.createCipheriv("aes-256-gcm", key.value, nonce);
+                    return Buffer.concat([
+                      PREFIX,
+                      nonce,
+                      cipher.update(value, "utf8"),
+                      cipher.final(),
+                      cipher.getAuthTag(),
+                    ]);
+                  },
+                  catch: (cause) =>
+                    new ElectronSafeStorage.ElectronSafeStorageEncryptError({ cause }),
+                }),
+            };
+          }
+          return yield* error;
+        }),
+      ),
+    );
+  });
+});
```
