From 2463d81e1990e71552472c6f29aa610557928145 Mon Sep 17 00:00:00 2001 From: Samuel K Date: Sat, 12 Sep 2026 12:18:41 -0600 Subject: [PATCH 01/13] fix(secrets): persist concrete backend ownership --- cmd/secrets/list.go | 4 +- desktop/src/main/ipc.ts | 1 + .../src/renderer/src/lib/stores/secrets.ts | 11 +- desktop/src/renderer/src/lib/types/index.ts | 1 + .../src/renderer/src/pages/SecretsPage.svelte | 10 +- pkg/secrets/backend_registry.go | 112 +++++++++++++ pkg/secrets/index.go | 20 +++ pkg/secrets/local_store.go | 147 +++++++++--------- pkg/secrets/store.go | 1 + pkg/secrets/store_internal_test.go | 35 +---- .../docs/developing-in-workspaces/secrets.mdx | 12 +- 11 files changed, 241 insertions(+), 113 deletions(-) create mode 100644 pkg/secrets/backend_registry.go diff --git a/cmd/secrets/list.go b/cmd/secrets/list.go index 90ba14014..0d4bb7196 100644 --- a/cmd/secrets/list.go +++ b/cmd/secrets/list.go @@ -37,6 +37,7 @@ func NewListCmd(flags *flags.GlobalFlags) *cobra.Command { type secretEntry struct { Name string `json:"name"` Context string `json:"context"` + Backend string `json:"backend,omitempty"` Created string `json:"created,omitempty"` LastUsed string `json:"lastUsed,omitempty"` Orphaned bool `json:"orphaned,omitempty"` @@ -93,6 +94,7 @@ func renderJSON(metas []secrets.SecretMeta) error { entries = append(entries, secretEntry{ Name: m.Name, Context: m.Context, + Backend: string(m.Backend), Created: formatTime(m.Created), LastUsed: formatTime(m.LastUsed), Orphaned: m.Orphaned, @@ -117,7 +119,7 @@ func formatTime(t time.Time) string { func orphanLabel(orphaned bool) string { if orphaned { - return "orphaned" + return "missing value" } return "ok" } diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index 3cd27d01a..5a6cd3a8a 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -42,6 +42,7 @@ interface SecretEntry { created?: string lastUsed?: string orphaned?: boolean + backend?: "keyring" | "file" } interface EnvEntry { diff --git a/desktop/src/renderer/src/lib/stores/secrets.ts b/desktop/src/renderer/src/lib/stores/secrets.ts index 14b5333d1..2b98afcbe 100644 --- a/desktop/src/renderer/src/lib/stores/secrets.ts +++ b/desktop/src/renderer/src/lib/stores/secrets.ts @@ -4,12 +4,14 @@ import type { Secret } from "$lib/types/index.js" export const secrets = writable([]) export const secretsLoading = writable(true) +export const secretsError = writable(null) export async function refreshSecrets(): Promise { try { secrets.set(await secretList()) - } catch { - // IPC not available + secretsError.set(null) + } catch (err) { + secretsError.set(err instanceof Error ? err.message : String(err)) } } @@ -17,8 +19,9 @@ export async function initSecrets(): Promise { secretsLoading.set(true) try { secrets.set(await secretList()) - } catch { - // IPC not available + secretsError.set(null) + } catch (err) { + secretsError.set(err instanceof Error ? err.message : String(err)) } finally { secretsLoading.set(false) } diff --git a/desktop/src/renderer/src/lib/types/index.ts b/desktop/src/renderer/src/lib/types/index.ts index 45f7a17a3..10c2b6f9b 100644 --- a/desktop/src/renderer/src/lib/types/index.ts +++ b/desktop/src/renderer/src/lib/types/index.ts @@ -151,6 +151,7 @@ export interface Secret { created?: string lastUsed?: string orphaned?: boolean + backend?: "keyring" | "file" } export interface EnvVar { diff --git a/desktop/src/renderer/src/pages/SecretsPage.svelte b/desktop/src/renderer/src/pages/SecretsPage.svelte index 19ce8eb75..75f4f0e6d 100644 --- a/desktop/src/renderer/src/pages/SecretsPage.svelte +++ b/desktop/src/renderer/src/pages/SecretsPage.svelte @@ -7,7 +7,7 @@ import { badgeVariants } from "$lib/components/ui/badge/index.js" import * as Dialog from "$lib/components/ui/dialog/index.js" import ConfirmDialog from "$lib/components/layout/ConfirmDialog.svelte" import CardSkeleton from "$lib/components/ui/skeleton/CardSkeleton.svelte" -import { secrets, secretsLoading, refreshSecrets } from "$lib/stores/secrets.js" +import { secrets, secretsError, secretsLoading, refreshSecrets } from "$lib/stores/secrets.js" import { secretSet, secretDelete } from "$lib/ipc/commands.js" import { toasts } from "$lib/stores/toasts.js" import { extractErrorMessage } from "$lib/utils/error.js" @@ -102,7 +102,7 @@ async function confirmDelete() { {nameExists ? "Replace Secret" : "Add Secret"} - Stored in your OS keyring and injected into workspaces on demand. + Stored securely in your configured Devsy secrets backend and injected into workspaces on demand.
{ e.preventDefault(); handleCreate() }} class="space-y-4"> @@ -158,6 +158,10 @@ async function confirmDelete() { {/each} + {:else if $secretsError} +
+ Unable to load secrets: {$secretsError} +
{:else if $secrets.length === 0}
@@ -189,7 +193,7 @@ async function confirmDelete() {
{#if secret.orphaned} - orphaned + Missing value {/if}