Skip to content

feat: switchable secret storage backends + README demo video - #3

Merged
starvy merged 4 commits into
mainfrom
docs/demo-video
Jul 21, 2026
Merged

feat: switchable secret storage backends + README demo video#3
starvy merged 4 commits into
mainfrom
docs/demo-video

Conversation

@starvy

@starvy starvy commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Two things, both needed before going public.

1. README demo video

The README had no visual of the app. Adds a 55s recording (GIF 728K inline + MP4).

demo

Recorded against a purpose-built workspace using only public, unauthenticated endpoints —
GitHub /zen, /octocat, /users/:u, /rate_limit for REST and the public Countries API for
GraphQL. No token anywhere, so no Authorization header is ever on screen. Beats: open a saved
request → Ctrl+Enter{{variables}} resolving against an environment → switch env and re-send
→ headers + timing waterfall → a GraphQL query → the command palette.

Also documents the Linux -dev packages GPUI links against. A clean Ubuntu 24.04 box could not
build with the previously documented steps — it fails in yeslogic-fontconfig-sys with "The
pkg-config command could not be found"
, which reads as a toolchain problem, not a missing package.

2. Switchable secret storage

Secrets could only come from the OS keychain. On Linux that isn't a preference — a headless box,
container or CI runner has no Secret Service at all, so every {{secret:..}} failed with
org.freedesktop.DBus.Error.ServiceUnknown. Reproduced on the dev box this was built on.

The SecretResolver seam already existed; only the keychain was wired, and the desktop app
hardcoded KeyringSecrets at ~8 sites. Now, behind a WorkspaceSecrets facade selected by
[secrets] in settings.toml and overridable per run with POSEL_SECRET_BACKEND:

Backend Writable Where values live
keychain (default, unchanged) yes OS keychain
file yes 0600 TOML in the user data dir, per-workspace, atomic writes
env no $POSEL_SECRET_<NAME> — for CI
command no stdout of pass/op/gh, git-credential-helper style
none secrets off

Read-only backends drop the Secrets panel's write controls instead of offering a guaranteed
failure. App Settings gains a picker; posel-cli gains --secret-backend. Errors now distinguish
"not set" from "backend unavailable" — the latter is what tells a user to switch backend.

Secret values still never enter the workspace; only the {{secret:name}} reference is written
to .toml.

Review + verification

CI can't run (Actions credits exhausted — it goes green once this repo is public), so this was
verified locally and by an adversarial review pass, which found six real defects that are fixed
in d6f8734
— two security-relevant:

  • the file backend chmod'ed 0600 after writing plaintext (measured 0664 under umask 002) and
    could strand a world-readable temp full of secrets; now created owner-only before a byte is written
  • SecretsError::Parse interpolated toml's error, which quotes the offending source line — in this
    file that line is a secret, and it surfaced in UI toasts and CLI stderr
  • the variables-grid secret→literal toggle wrote plaintext into the workspace on read-only backends
  • the helper command ran unbounded on the UI thread (a pinentry prompt froze the app)
  • AppSettings::load() ran per row per frame in the Secrets panel
  • a typo'd backend name silently reset all settings

Verified:

  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace401 passed, 0 failed
  • Windows cross-compile (x86_64-pc-windows-gnu, clippy -D warnings) on storage/cli/core/template — this is what exercises the #[cfg(not(unix))] branch, which had never been compiled anywhere
  • End-to-end on the wire: env, command and file backends each resolve into a real request's Authorization header; 0600 confirmed on disk with no stray temps
  • GUI checked under Xvfb: the backend picker, and read-only backends correctly hiding write controls

macOS is not compile-verified (no SDK here), but it takes the same #[cfg(unix)] path as Linux and
the only macOS-specific surface — keyring's apple-native feature — is untouched.

Known, not fixed here

Two pre-existing bugs found while testing, worth follow-ups:

  • macOS glyphs leak into the Linux/Windows UI — the default empty state reads "Press ⌘↵ to send" (4 sites bypass the existing keybinding_hint())
  • no window_min_size; below ~900px the response footer overlaps itself

starvy added 4 commits July 21, 2026 14:47
The README had no visual of the app, which was blocking release. Adds a
55s screen recording (GIF for inline rendering, MP4 for higher quality)
covering the core loop: open a saved request, send with Ctrl+Enter,
resolve {{variables}} against an environment, inspect headers and the
timing waterfall, run a GraphQL query, and open the command palette.

Also documents the Linux -dev packages GPUI links against. A clean
Ubuntu 24.04 box cannot build with the previously documented steps — it
fails in yeslogic-fontconfig-sys with "The pkg-config command could not
be found", which reads as a toolchain problem rather than a missing
system package.
Secrets could only ever come from the OS keychain, which is not merely a
preference on Linux — a headless box, container, or CI runner has no
Secret Service at all, so every {{secret:..}} failed with:

    Platform secure storage failure: org.freedesktop.DBus.Error.ServiceUnknown:
    The name org.freedesktop.secrets was not provided by any .service files

The SecretResolver seam already existed; only the keychain was wired, and
the desktop app hardcoded KeyringSecrets at ~8 call sites. This adds four
more backends behind a WorkspaceSecrets facade, selected by `[secrets]` in
settings.toml and overridable per run with POSEL_SECRET_BACKEND:

- keychain (default, writable) — unchanged behavior
- file (writable) — 0600 TOML in the user data dir, namespaced per
  workspace, written atomically via a temp file chmod'ed before it holds a
  value. Plaintext at rest, matching ~/.aws/credentials and gh's hosts.yml;
  the deliberate trade that makes the Secrets panel usable with no keychain
- env (read-only) — $POSEL_SECRET_<NAME>, for CI
- command (read-only) — argv per lookup, stdout is the value, git
  credential-helper style, for pass/1Password/gh
- none — secrets off

Backends split into writable and read-only: the Secrets panel drops its
Add/Set/Remove controls rather than offering a guaranteed failure, and
App Settings gains a Secret storage picker whose hint tracks the choice.
Errors now distinguish "not set" from "backend unavailable" — the latter
is what tells a user to switch backend rather than set a value.

Secret values still never enter the workspace: only the {{secret:name}}
reference is written to .toml, so collections stay safe to commit.
Collapses a block introduced while reworking the error message. Caught by
cargo fmt --all --check, which CI enforces.
…eaks

Pre-merge review of the pluggable-secrets change found six real defects,
two of them security-relevant. Fixes, highest severity first:

- The file backend chmod'ed 0600 *after* writing the plaintext, so
  fs::write created it 0666 & ~umask (measured 0664 here) with every
  secret already in it — and left a world-readable secrets.toml.tmp
  behind forever if the process died in between. README and the code
  comment both claimed the opposite. Now created owner-only via
  OpenOptions::mode() before a byte is written, fsync'ed for durability,
  temp name carries the pid so concurrent writers can't collide, and a
  failed rename cleans up rather than stranding a file full of secrets.

- SecretsError::Parse interpolated toml's error, whose Display quotes the
  offending source line — in this file that line *is* a secret, and the
  string reaches UI toasts and CLI stderr. Now reports location only.

- The variables grid's secret→literal toggle wrote plaintext into the
  workspace .toml while the delete silently failed on read-only backends,
  breaking the feature's core promise. Refused there, and a failed read no
  longer blanks the cell and then deletes the entry.

- The helper command ran unbounded on the UI thread: a pass/op pinentry
  prompt froze the app with no way out. Now killed after 20s, stdin
  closed, pipes drained concurrently so a chatty helper can't deadlock.

- The Secrets panel called AppSettings::load() — a file read and TOML
  parse — once per row per frame; resolved once into the snapshot instead.

- A typo'd backend name failed the whole settings parse, silently
  resetting theme, font scale and history limits; unknown values now
  degrade to the default and leave the rest intact.

Also drops a stale "in keychain" toast and doc comments that outlived the
keychain-only design. Six regression tests, including ones that assert the
mode while the plaintext is on disk and that the raw toml error really does
leak (so the wrapper's silence is meaningful).
@starvy starvy changed the title docs(readme): add demo video and Linux build prerequisites feat: switchable secret storage backends + README demo video Jul 21, 2026
@starvy
starvy merged commit c7fa35f into main Jul 21, 2026
0 of 3 checks passed
@starvy
starvy deleted the docs/demo-video branch July 21, 2026 17:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant