Skip to content

feat(secrets): first-party keyring commands and secrets panel - #448

Open
duketopceo wants to merge 8 commits into
omacom:quattrofrom
duketopceo:feat/secrets-panel
Open

duketopceo wants to merge 8 commits into
omacom:quattrofrom
duketopceo:feat/secrets-panel

Conversation

@duketopceo

@duketopceo duketopceo commented Sep 15, 2026 •

Copy link
Copy Markdown

Omarchy gains a first-party way to browse and manage the system Secret Service keyring — Setup → Security → Secrets opens a panel over the shared org.freedesktop.Secret namespace, so credentials written by keytar-based editors, GitHub CLI, Seahorse, or omaseal are visible and manageable in one place for the first time.

Related: #447

What ships

  • bin/omarchy-secrets-{get,set,list,delete,clipclear} — python-gobject commands over D-Bus. set reads the value from stdin (never argv) and updates every item matching a service/account pair; list prints JSONL metadata only, never secret values; delete removes every match and re-verifies; clipclear clears the clipboard only while it still holds the addressed secret.
  • shell/plugins/secrets (omarchy.secrets) — virtualized list, vault dropdown with per-service counts, name/recent sort, / filter, ext/other app provenance badges, keyboard-first (j/k, Enter copies, x confirms-delete, v/s/r/a).
  • Menu entry under Setup → Security.
  • Tests: source-assertion panel test plus a behavioral suite that drives the five commands against a stubbed gi backend (no keyring needed).

Design decisions

  • Shared namespace, not an omarchy silo. Search uses a DONT_MATCH_NAME generic schema on service/account so items other tools wrote are found; new items are stamped app=omarchy and foreign items carry a badge plus a "Managed by X" disclosure on delete. set/delete deliberately act on every matching item so duplicates written by different tools stay coherent rather than silently diverging.
  • Secrets never touch QML state or argv. Copy is v=$(omarchy-secrets-get …) && printf %s "$v" | wl-copy --sensitive — two-stage so a failed lookup cannot clobber the clipboard or report false success. The add form writes to the child's stdin after start (stdin is re-armed per run — a finished Process keeps its closed channel).
  • The 30s clipboard clear outlives the panel. It is a detached systemd-run --user --on-active=30 unit rather than an in-panel timer, so copy-then-dismiss still clears; clipclear re-verifies the clipboard contents against every same-pair value before clearing, so a newer user copy is never destroyed.
  • Failure honesty. Backend stderr is surfaced only on nonzero exit (success diagnostics like updated 1 item(s) are not errors), a stall timer names a locked-keyring hang instead of spinning forever, and process exits after close cannot repopulate a wiped panel.

Verification

  • test/shell.d/secrets-commands-test.sh: 24 behavioral assertions against a stubbed Secret Service (round-trip, duplicate coherence, provenance stamp, conditional clear, post-delete verify).
  • test/shell.d/secrets-panel-test.sh: 45 source-contract assertions (identity capture before async exit, stdin re-arm, picker/key guards, detached clear).
  • bin/omarchy commands --check: passes (474 commands); ./test/cli: clean; ./test/shell: clean except pre-existing environmental failures that need a sibling omarchy-pkgs checkout (identical on a clean tree).
  • Live on aarch64 (Asahi): full set → get → list → copy → conditional clear → delete round-trip against the real keyring, and the panel verified in the running shell. Nothing is arch-specific — python-gobject + wl-clipboard behave identically on x86.

Notes for reviewers

  • omarchy-secrets-clipclear is # omarchy:hidden=true (panel-internal helper, like the clipboard paste helpers).
  • The set/delete act-on-all-duplicates contract means the panel can mutate items owned by other applications when the service/account pair collides; that is intentional for namespace coherence, and the UI discloses foreign ownership before delete. Flagging in case maintainers want an --only-omarchy mode instead.

Generated with Devin

  • Deferred (non-blocking): the list/filter/sort logic could be extracted to a Model.js (sibling-panel convention) for behavioral unit tests; pendingCopy re-reads the selection at fire time (a moved selection copies the new row — arguably correct); non-UTF-8 secrets can't round-trip through the clipboard compare.

duketopceo and others added 5 commits September 14, 2026 01:10
Panel gains vault dropdown with per-service counts, name/recent sort,
external-credential badges, coalesced copy, and a 30s conditional
clipboard clear via omarchy-secrets-clipclear, which clears only while
the clipboard still holds the copied secret.

Commands: set stamps app=omarchy on create and updates every duplicate
of a service/account pair; delete removes every match so copies left by
other tools cannot keep the credential readable; list emits app.
…uards

- Re-arm setProc.stdinEnabled per run; a finished process kept the closed
  channel so every add after the first starved the child's stdin read.
- Copy is two-stage (v=$(get) && printf | wl-copy) so a failed lookup can
  neither clobber the clipboard nor masquerade as a successful copy.
- The 30s clipboard clear is a detached systemd --on-active timer: it
  survives copy-then-dismiss, and clipclear still re-verifies the clipboard
  holds that secret before clearing (now against every same-pair value).
- Delete runs clipclear first, while the keyring copy still exists to
  compare against, then removes every matching item.
- stderr surfaces only on nonzero exit; success diagnostics like
  "updated 1 item(s)" no longer flash as errors.
- Process exits after close() no longer repopulate or notice a wiped
  panel; a stall timer names a locked-keyring hang instead of spinning.
- Keys and clicks respect the vault picker: no hidden-list activation,
  and a click copies the clicked row rather than the keyboard selection.
- The all-vaults state is a null sentinel; a service named "*" stays
  filterable. Picker rows virtualize.
- set/delete tolerate per-item keyring errors and report partial counts;
  clipclear is hidden; list sort tolerates null labels.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The keybind footer Text had no width bound or elide — at the 560px card it
painted past the right edge, clipping `esc close`. Tightened separators and
added ElideRight so hints stay inside at any width.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@malik-na malik-na added the lvl 0 Core userspace: display, audio, video, graphics, memory, storage, battery, keyboard, network, BT. label Sep 18, 2026
@laihenyi

Copy link
Copy Markdown

Independent review (AI-assisted, Claude). Read the five commands, the panel's process handling and both tests; ran the QA set in an Ubuntu 24.04 arm64 container. I could not exercise the QML panel against a live shell.

Scope question for the maintainers

Nothing here is Mac-specific (python-gobject over Secret Service, quickshell panel). The same design was proposed in discussion #447 with no replies yet. Since omarchy-mac tracks upstream omacom/omarchy and this adds five commands plus a 790-line panel, it may be worth deciding whether this should land upstream first and be synced, or be carried here. That is a project call, not a code problem.

Dependencies are already in the base set

gnome-keyring, libsecret, python-gobject and wl-clipboard are all in install/omarchy-base.packages, so no new packages are needed.

One real defect: trailing newline breaks the timed clipboard clear

  • omarchy-secrets-set stores stdin verbatim (sys.stdin.read()), so echo tok | omarchy secrets set svc acct stores "tok\n".
  • The panel's copy runs v=$(omarchy-secrets-get "$1" "$2") && printf %s "$v" | wl-copy --sensitive; command substitution strips trailing newlines, so the clipboard gets "tok".
  • omarchy-secrets-clipclear compares wl-paste -n output ("tok") against the stored values ({"tok\n"}), finds no match and returns without clearing.

Net effect: any secret created from the CLI with a trailing newline is copied without it and then never auto-cleared. Secrets added through the panel form are unaffected (write(secretField.text) has no newline). Two easy fixes, either works: strip a single trailing \n in set (documenting that echo users get the intended value), or have clipclear also accept value.rstrip("\n") matches. A test for the echo-style input would pin it.

Smaller observations (non-blocking)

  • list searches with SearchFlags.UNLOCK, so opening the panel on a locked keyring triggers the unlock prompt just to browse. Intentional per the stall-timer comment, just noting it.
  • get prints the secret to stdout by design; from an interactive terminal that lands in scrollback. The summary says so, which is enough.
  • Delete runs clipclear … || true; exec omarchy-secrets-delete, so a deleted secret cannot stay on the clipboard. Good.
  • set updates every matching item, including ones written by other tools. That is the stated interop contract, but it does mean an omarchy edit silently rewrites a keytar/gh item with the same service/account; the "Managed by X" disclosure exists on delete but not on overwrite.

Local verification (Ubuntu 24.04 arm64 container)

  • Syntax loop: pass; shellcheck on bin/omarchy only reports pre-existing warnings unrelated to this change
  • bin/omarchy commands --check: 474 commands pass
  • test/shell.d/secrets-commands-test.sh: 24 checks pass
  • test/shell.d/secrets-panel-test.sh: 41 checks pass

@malik-na malik-na added the US label Sep 20, 2026
…es in clipclear

Review on omacom#448 found the timed clipboard clear never fired for
echo-created secrets: set stored stdin verbatim (with newline) while
command substitution stripped it on copy, so clipclear's exact match
failed. set now strips one trailing newline (matching omaseal set), and
clipclear also accepts stripped-form matches for entries stored before
the normalization.

Also disclose on overwrite when an update touches items not stamped
app=omarchy, mirroring the 'Managed by' disclosure on delete.
@duketopceo

Copy link
Copy Markdown
Author

Thanks for the thorough review — the trailing-newline defect was a real one and is fixed in cb227be5.

Defect — fixed. set now strips one trailing newline after reading stdin (echo secret | idiom), matching what omaseal set does, so stored values are canonical. clipclear also matches the stripped form, so entries stored before this change still auto-clear. Four new checks pin it: newline stripped on store, newline-only stdin rejected, get returns the clean value, and a legacy stored-with-\n entry still clears from the clipboard.

Foreign-item overwrite disclosure — added. set now reports updated N item(s) ... (M written by other tools) when an update touches items not stamped app=omarchy, mirroring the "Managed by" disclosure on delete.

Scope question — agree it's a maintainer call. Position from this side: nothing here is Mac-specific by design (python-gobject + Secret Service + quickshell, all already in install/omarchy-base.packages as you noted). Landing it here first lets the fork ship the feature and shake out real-world behavior on a live keyring; the same diff should be straightforward to propose upstream afterward — happy to help prep that if the maintainers want it there first instead. Discussion #447 has been quiet, so whichever direction lands, this PR is the reference implementation either way.

Noted, unchanged:

  • list searching with UNLOCK is intentional — the panel treats browse-time unlock as the cost of seeing real values vs. an empty pane.
  • get printing to stdout is the documented pipe contract (omarchy secrets get … | …); scrollback exposure is called out in the summary, same tradeoff secret-tool lookup makes.

A locked collection listed as an empty vault ("No secrets stored") and
gave the user no way forward. Worse, the list call itself can spawn a
prompter — which this Overlay-layer, exclusive-keyboard-grab window
covers and starves of input.

Add omarchy-secrets-status (read-only lock probe) and
omarchy-secrets-unlock (blocking unlock via the system prompter). The
panel probes status before listing, shows "Keyring locked" with an
Unlock action (button or u key) instead of the empty-vault text, and
refuses add while locked. requestUnlock detaches the call and dismisses
the panel so the prompter gets the screen and keys; the next summon
sees the unlocked keyring.

This branch has not been deployed

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

Labels

lvl 0 Core userspace: display, audio, video, graphics, memory, storage, battery, keyboard, network, BT. US

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants