feat: switchable secret storage backends + README demo video - #3
Merged
Conversation
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).
Recorded against a purpose-built workspace using only public, unauthenticated endpoints —
GitHub
/zen,/octocat,/users/:u,/rate_limitfor REST and the public Countries API forGraphQL. No token anywhere, so no
Authorizationheader is ever on screen. Beats: open a savedrequest →
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
-devpackages GPUI links against. A clean Ubuntu 24.04 box could notbuild with the previously documented steps — it fails in
yeslogic-fontconfig-syswith "Thepkg-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 withorg.freedesktop.DBus.Error.ServiceUnknown. Reproduced on the dev box this was built on.The
SecretResolverseam already existed; only the keychain was wired, and the desktop apphardcoded
KeyringSecretsat ~8 sites. Now, behind aWorkspaceSecretsfacade selected by[secrets]insettings.tomland overridable per run withPOSEL_SECRET_BACKEND:keychain(default, unchanged)file0600TOML in the user data dir, per-workspace, atomic writesenv$POSEL_SECRET_<NAME>— for CIcommandpass/op/gh, git-credential-helper stylenoneRead-only backends drop the Secrets panel's write controls instead of offering a guaranteed
failure. App Settings gains a picker;
posel-cligains--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 writtento
.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:filebackend chmod'ed0600after writing plaintext (measured 0664 under umask 002) andcould strand a world-readable temp full of secrets; now created owner-only before a byte is written
SecretsError::Parseinterpolated toml's error, which quotes the offending source line — in thisfile that line is a secret, and it surfaced in UI toasts and CLI stderr
pinentryprompt froze the app)AppSettings::load()ran per row per frame in the Secrets panelVerified:
cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace→ 401 passed, 0 failedx86_64-pc-windows-gnu, clippy-D warnings) onstorage/cli/core/template— this is what exercises the#[cfg(not(unix))]branch, which had never been compiled anywhereenv,commandandfilebackends each resolve into a real request'sAuthorizationheader;0600confirmed on disk with no stray tempsmacOS is not compile-verified (no SDK here), but it takes the same
#[cfg(unix)]path as Linux andthe only macOS-specific surface —
keyring'sapple-nativefeature — is untouched.Known, not fixed here
Two pre-existing bugs found while testing, worth follow-ups:
keybinding_hint())window_min_size; below ~900px the response footer overlaps itself