Files
omarchy/plugins/io.github.elevate08.qs-bitwarden-cli/tasks/plan.md
T
asepharyana 1cdb82a76f Sync config from arch
- hypr/apps.lua
- hypr/autostart.lua
- hypr/envs.lua
- hypr/hyprland.lua
- hypr/hyprsunset.conf
- hypr/input.lua
- hypr/looknfeel.lua
- hypr/omasettings.lua
- hypr/xdph.conf
- omarchy/branding/about.txt
- omarchy/branding/screensaver.txt
- omarchy/extensions/omarchy-menu.jsonc
- omarchy/hooks/battery-low.d/play-warning-sound.sample
- omarchy/hooks/font-set.d/show-font-notification.sample
- omarchy/hooks/post-boot.d/weather.sample
- omarchy/hooks/post-update.d/install-voxtype.hook
- omarchy/hooks/post-update.d/setup-agent.hook
- omarchy/hooks/post-update.d/setup-fingerprint.hook
- omarchy/hooks/post-update.d/show-update-notification.sample
- omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
- omarchy/hooks/theme-set.d/show-theme-notification.sample
- omarchy/shell.json
- omarchy/shell.toml
- omarchy/theme.name
- omarchy/themes/azure-glow/README.md
- omarchy/themes/azure-glow/alacritty.toml
- omarchy/themes/azure-glow/btop.theme
- omarchy/themes/azure-glow/hyprland.conf
- omarchy/themes/azure-glow/hyprlock.conf
- omarchy/themes/azure-glow/icons.theme
- … 269 more
2026-09-23 15:19:12 +07:00

522 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Implementation Plan: Opt-in Bitwarden SSH Agent
## Overview
Build the SSH-agent design in `docs/ideas/ssh-agent.md` as an optional,
disabled-by-default feature. The existing Quickshell panel remains the only
component that invokes `bw` and owns `BW_SESSION`; a supervised Rust companion
implements the local SSH-agent protocol, holds private SSH keys only while the
vault is unlocked, and makes signing contingent on a live approval or bounded
process grant. Before any agent work, the ordinary vault read is sanitized
outside QML so unsupported cipher types and `sshKey.privateKey` cannot enter
the long-lived panel process.
Detailed tasks and checkpoints are tracked in `tasks/todo.md`. No feature code
should be written until this plan has human approval.
## Development Worktree and Live Checkpoints
- Develop on branch `feature/ssh-agent` in `.worktrees/ssh-agent`, based on the
latest fetched `origin/master`. Keep the primary `master` checkout free of
feature changes.
- The enabled Omarchy plugin path
`~/.config/omarchy/plugins/io.github.elevate08.qs-bitwarden-cli` must resolve
to this worktree before any live test.
- At the end of every task slice, run its focused tests plus the applicable
regression/QML/lint/manifest gates, verify the plugin symlink target, run
`omarchy restart shell`, confirm `omarchy-shell shell ping`, summon the
Bitwarden panel, and call its non-secret `status` IPC method.
- Pause after that live reload so the user can exercise the plugin manually.
Do not start the next task until the user confirms the checkpoint or reports
issues to fix.
## Scope
The first release includes:
- a public-only, read-only SSH-key item experience in the panel;
- Ed25519 and RSA SHA-2 authentication/signing through a stable Unix socket;
- explicit per-signature approval and short per-process grants;
- opt-in unlock-on-demand, safe UWSM client routing, and advisory diagnostics;
- public-key file projection for normal Git SSH-signing configuration;
- an x86_64 GNU helper bundled with the plugin and verified by reproducible CI.
It does not include key creation/import, SSH item editing/cloning, master-
password re-prompt keys, forwarding, persistent grants, GPG-agent support,
item types 6–8, aarch64, or a Bitwarden SDK/direct service integration.
## Architecture Decisions
- Sanitize `bw list items` with static `jq` programs before QML parses it. QML
receives intact types 1–4 and a public-only type-5 projection; all other
types fail closed out of the display model.
- Keep one panel-owned `bw list items` read per unlock/sync. When the agent is
enabled, a bounded `tee` branch sends only eligible type-5 private material
to a nonce-framed FIFO. Failure of that optional branch must never prevent
the panel list from loading.
- Use one Rust companion and one stable socket under `$XDG_RUNTIME_DIR`. The
companion spawns no child processes, receives neither `BW_SESSION` nor
unrelated vault data, and exits when its control channel closes.
- Make approval, epoch checks, request limits, and final allow/deny decisions
authoritative in the companion. Process information is prompt context, not
an authentication boundary.
- Treat public-key file export as v1 scope because normal Git SSH signing needs
file paths. The planning assumption is that the companion owns this
projection from its validated public keystore; Task 4 records or corrects
that ownership before implementation proceeds.
- Ship only `x86_64-unknown-linux-gnu` initially. Commit the source, lockfile,
pinned toolchain, release bytes, and checksum; compare clean CI output byte
for byte before anything reaches `master` or a release.
## Dependency Graph
```text
Task 1 sanitized read contract
└── Task 2 panel SSH-key slice
└── Task 3 prerequisites and diagnostics
Task 4 Rust dependency/security decision
└── Task 5 protocol and signing
└── Task 6 epoch keystore
├── Task 7 nonce FIFO loading
└── Task 8 approvals and grants (also depends on Task 5)
└── Task 9 companion lifecycle (also depends on Task 7)
Tasks 3 + 9
└── Task 10 QML supervision
├── Task 11 opt-in setup
└── Task 12 shared vault fan-out (also depends on Tasks 1 + 7)
└── Task 13 vault lifecycle (also depends on Tasks 8 + 9)
├── Task 14 approval UI
└── Task 15 public-key projection
Task 9
└── Task 16 reproducible build
└── Task 17 pull-request gates
Tasks 10 + 16
└── Task 18 bundled-helper validation
└── Task 19 protected release (also depends on Task 17)
Tasks 3 + 11 + 14 + 15 + 19
└── Task 20 documentation and release validation
```
## Task List
### Phase 1: Remove the Existing Exposure
- [x] Task 1: Introduce the sanitized vault-read contract
- [x] Task 2: Deliver the public SSH-key panel slice
- [x] Task 3: Enforce SSH support prerequisites
### Checkpoint: Safe Panel Baseline
- [ ] QML-facing fixture output contains no SSH private-key marker or unknown
cipher-type marker.
- [ ] SSH keys are public-only and cannot reach generic detail/edit paths.
- [ ] All current JavaScript and QML tests pass; the plugin validates and lints
at its existing baseline.
- [ ] Human review approves the security boundary before Rust work continues.
### Phase 2: Prove the Headless Agent Core
- [x] Task 4: Pin the Rust security foundation
- [x] Task 5: Implement bounded SSH protocol signing
- [x] Task 6: Implement the vault-epoch keystore
### Checkpoint: Rust Primitives
- [x] Ed25519 and both RSA SHA-2 modes pass protocol-vector tests.
- [x] Lock, malformed input, mismatch, and limit paths fail closed.
- [x] The dependency/zeroization decision is recorded and reviewed.
- [x] Task 7: Implement nonce-framed FIFO loading
- [x] Task 8: Authorize signatures with bounded grants
- [x] Task 9: Complete the supervised companion lifecycle
### Checkpoint: Headless Companion
- [ ] Disposable-key tests cover identities, signing, multiple clients,
approval, grants, lock races, and FIFO rejection.
- [ ] The helper serves only its private stable socket, has no vault credential,
spawns no process, and exits on control EOF.
- [ ] Manual headless authentication and Git SSH-signing smoke tests pass.
- [ ] Human review approves proceeding to panel integration.
### Phase 3: Integrate the Optional Feature
- [x] Task 10: Establish companion supervision
- [x] Task 11: Deliver opt-in session setup
- [x] Task 12: Feed the companion from the shared vault read
### Checkpoint: Optional Data Plane
- [x] Disabled mode starts no helper and creates no socket, FIFO, or private-key
branch.
- [x] Enabled mode loads keys from the same vault read without delaying or
breaking the ordinary list on helper failure.
- [x] QML remains responsive during blocked SSH clients and helper events.
- [x] Task 13: Enforce vault lifecycle transitions
- [x] Task 14: Deliver signing authorization UX
- [x] Task 15: Project validated public-key files
### Checkpoint: End-to-End Feature
- [x] Lock, logout, account change, suspend, screen lock, disable, crash, and
Quickshell reload all satisfy the state machine.
- [x] Authentication, commit signing, and multi-commit signing work with no
private key on disk.
- [x] Public projections survive lock but clear on logout/account change/disable.
- [ ] Human security and usability review approves packaging.
### Phase 4: Establish the Artifact Trust Path
- [x] Task 16: Make the release build reproducible
- [x] Task 17: Add read-only pull-request gates
### Checkpoint: Candidate Artifact
- [x] Two clean pinned builds produce identical stripped bytes.
- [x] Existing, Rust, audit, license, and end-to-end gates pass with no PR
secrets or write token.
- [x] Task 18: Validate the bundled helper at launch
- [~] Task 19: Protect release provenance -- workflow, environment and tests
are in place; the first protected tag run is still outstanding
### Checkpoint: Shippable Artifact
- [ ] The tracked helper is executable, native-tested, checksum-consistent,
protocol-compatible, and byte-identical to the protected build.
- [ ] A release produces provenance attestation, SBOM, and dependency/license
report with narrowly scoped permissions.
### Phase 5: Document and Release
- [~] Task 20: Complete release documentation -- README, CHANGELOG, manifest
and the design draft are done; the manual release matrix is not
### Checkpoint: Complete
- [ ] Every task's acceptance criteria and the project Definition of Done pass.
- [ ] Setup, conflict, logout/login, upgrade, verification, and uninstall paths
are documented and manually exercised.
- [ ] No private key, session token, signed payload, or signature appears in
QML state, argv, logs, files, CI artifacts, or test diagnostics.
- [ ] Human review approves merge and release.
## Verification Strategy
Use focused tests after each task and these full gates at checkpoints:
```bash
for test_file in tests/*.test.js; do node "$test_file" || exit 1; done
QT_QPA_PLATFORM=offscreen /usr/lib/qt6/bin/qmltestrunner -input tests/qml
mkdir -p /tmp/qs-imports
ln -sfn /usr/share/omarchy/shell /tmp/qs-imports/qs
/usr/lib/qt6/bin/qmllint -I /tmp/qs-imports Panel.qml FormPickerRow.qml
omarchy plugin validate .
cargo fmt --manifest-path agent/Cargo.toml --check
cargo clippy --manifest-path agent/Cargo.toml --locked --all-targets -- -D warnings
cargo test --manifest-path agent/Cargo.toml --locked --all-targets
cargo deny --manifest-path agent/Cargo.toml check
```
The final runtime matrix must include a disposable fixture vault, a fake panel
controller, real OpenSSH clients, Git authentication, Git SSH signing, lock
during every sensitive transition, helper crash/reload, and both unlock-on-
demand modes. CI must never use a real vault or credential.
## Parallelization Opportunities
- After Task 3, the panel baseline can remain stable while Tasks 4–9 build the
headless Rust core; do not parallelize work that changes the sanitized read
contract until Task 1 is merged.
- After Task 9, Task 16's reproducible-build work can run alongside Tasks
10–11 because it consumes the frozen helper interface rather than QML state.
- After Task 13, Tasks 14 and 15 can proceed independently if Task 4 has fixed
the public-export owner and the control protocol is frozen.
- Tasks 17 and 18 may proceed in parallel after Task 16, but Task 19 waits for
both so release policy matches launch-time validation.
## Risks and Mitigations
| Risk | Impact | Mitigation |
|---|---|---|
| QML receives private SSH material before sanitization | High | Make Tasks 1–3 a standalone prerequisite gate with marker-based real-pipeline tests. |
| Selected Rust key types retain secret clones after lock | High | Fail the Task 4/6 spike if parsed private components cannot be bounded and best-effort-zeroized. |
| A lock races a load, approval, grant, or signature | High | Use vault epochs, an atomic deny linearization point, candidate keystores, bounded acknowledgments, and kill-on-timeout. |
| Same-UID FIFO injection replaces the key set | High | Require a fresh 128-bit load nonce delivered only over the private control pipe and reject duplicate/stale payloads. |
| A slow or broken agent branch stalls the core vault list | High | Bound input/output/time, drain eagerly, supervise one process group, and retry the list once without the optional branch. |
| Process attribution creates false security confidence | Medium | Enforce peer UID; present PID/path/start-time as context only; scope grants narrowly and revalidate at sign time. |
| UWSM setup overwrites another agent configuration | High | Use one plugin-owned atomic file, refuse symlinks/unexpected contents, show conflicts, and require confirmation plus re-login. |
| Bundled binary drifts from source | High | Pin every build input and compare clean release bytes byte-for-byte before merge/release. |
| Fork PR artifact policy becomes impossible to satisfy | Medium | Upload read-only candidates for source-only fork PRs; require the matching bytes before merge to `master`, not inside the untrusted fork job. |
| Older/self-hosted servers expose partial SSH support | Medium | Enforce the CLI floor, report capability as unconfirmed when appropriate, and document the 2026.8.0 malformed-item fix. |
| Feature complexity degrades the ordinary vault | High | Keep disabled mode inert, isolate helper failure, checkpoint every 2–3 tasks, and run the full existing regression suite throughout. |
## Open Questions for Human Review
- **Public export scope:** Revision 2 says both that public-key export is
required in v1 and that it remains later work. This plan follows the stronger
end-to-end requirement and includes it in v1. Confirm that choice.
- **Public export owner:** This plan recommends the companion write the
projection from its validated public keystore, using an absolute data path
supplied at launch. Task 4 must record the final owner and its path/cleanup
contract before implementation.
- **Dependency outcome:** The Rust crate set, Bitwarden v2 agent status, RFC
status, zeroization behavior, and license/audit surface must be re-verified
from current primary sources during Task 4; the design draft's review date is
not sufficient evidence.
- **Release governance:** *Resolved during Task 19.* The protected environment
is `release`; its required reviewer is `@Elevate08`, with self-review
permitted because this repository has one maintainer -- the gate exists so
that write, OIDC and attestation credentials never come into existence
without a deliberate human click, not to simulate a second pair of eyes that
does not exist. Deployments are restricted to `v*` tags. CODEOWNERS names
`@Elevate08` throughout, and calls out `/.github/workflows/release.yml`
separately as the only workflow that can write, mint a token, or sign.
## Follow-up Plan: Centered SSH Approval Popup
The approved follow-up specification is
`docs/ideas/ssh-approval-popup.md`. Add a disabled-by-default presentation
choice without changing the companion protocol or authorization rules.
### Dependency order
```text
setting contract
-> centered approval surface
-> locked-vault unlock surface
-> documentation and full verification
```
### Architecture decisions
- Keep request and credential state in `Panel.qml`; popup components are views
that call the same approve, deny, PIN, fingerprint, and password functions.
- Use a full-output transparent `PanelWindow`, `ExclusionMode.Ignore`, the
Wayland overlay layer, and a brief Exclusive-to-OnDemand focus prime,
matching Omarchy's centered transient surfaces.
- Reuse `SshApprovalScreen.qml` between both presentation modes so the key,
process, grant, loading, and deadline semantics cannot drift.
- Keep account login in the full panel. The centered prompt handles only the
signed-in locked/unlocked states involved in SSH requests.
### Risks and mitigations
| Risk | Mitigation |
|---|---|
| Popup accidentally becomes a second authorization authority | It only calls existing `Panel.qml` request functions; helper checks remain unchanged. |
| PIN/fingerprint results are discarded because the anchored panel is closed | Define one transient-auth-surface predicate used by all unlock acceptance checks. |
| The invisible experience leaves a password or pending process behind | Dismissal denies the request and runs popup-specific credential/prewarm cleanup. |
| A request label injects markup | Every request-derived `Text` remains `Text.PlainText`. |
| Overlay traps input after the request ends | Visibility and Wayland keyboard focus bind directly to the live pending request. |
---
# Implementation Plan: Colorized Menu-Bar Icon
## Overview
Add an opt-in `colorizeIcon` boolean setting to the Bitwarden panel. When
enabled, the primary menu-bar shield follows Omarchy's live `Color.accent`
theme color; when disabled, it keeps the current bar foreground color. The
locked padlock, missing-dependency badge, and urgent/error indicators remain
unchanged so color personalization does not erase status meaning.
The existing settings renderer already turns boolean schema entries into
keyboard-operable toggle rows. No custom color picker, palette model, new
dependency, or arbitrary color persistence is required.
## Architecture Decisions
- Store only `colorizeIcon: boolean`; derive the actual color from
`Color.accent` at render time so theme changes are inherited automatically.
- Default the setting to `false` so existing installations retain their current
appearance.
- Apply the setting only to the primary shield glyph in `shieldIconComp`.
Leave the padlock and setup/error badges on their existing bindings.
- Put the row in the General settings group, using the existing schema-driven
toggle and `omarchy bar set` persistence path.
- Treat malformed external values as `false`, using the existing strict
boolean-setting behavior.
## Dependency Graph
```text
manifest default/schema + model schema
│
├── settings persistence/value lookup tests
│
└── Panel color binding + schema-driven General toggle
│
└── QML/static checks + runtime/theme verification
```
## Task List
### Phase 1: Settings Contract
- [x] Task 1: Add the colorization setting contract
### Checkpoint: Settings Contract
- [x] `colorizeIcon` exists in both `manifest.json` and `BitwardenModel.js`
with boolean type and a `false` default.
- [x] Valid booleans round-trip through the existing writer, while malformed
values resolve to `false`.
- [x] Focused settings/model tests pass before UI wiring begins.
### Phase 2: Vertical UI Slice
- [x] Task 2: Wire the theme-accent shield and General toggle
### Checkpoint: Colorized Icon Behavior
- [x] With the setting off, the primary shield still uses the bar foreground.
- [x] With the setting on, the primary shield uses `Color.accent`.
- [x] Lock/setup/error badges retain their existing foreground or urgent colors.
- [x] The settings row is keyboard-operable and persists through the existing
shell reload path.
### Phase 3: Verification and Documentation
- [~] Task 3: Add regression coverage and document the setting
### Checkpoint: Complete
- [x] Focused and full JavaScript tests pass.
- [x] QML tests/lint and plugin validation pass where the Omarchy environment is
available.
- [ ] Manual verification covers both toggle states, a theme accent change,
locked state, setup-required state, and an error/urgent state.
- [x] README or user-facing feature documentation explains the toggle and its
theme-derived behavior.
- [ ] Human review approves the implementation before merge; no commit or push
is performed automatically.
## Task Details
### Task 1: Add the colorization setting contract
**Description:** Register `colorizeIcon` as a General boolean setting in the
manifest and model schema, expose its default through the widget defaults, and
ensure the existing settings value/read/write paths recognize it without
special-case persistence code.
**Acceptance criteria:**
- [x] The manifest and model declare the same `colorizeIcon` key, boolean type,
label, description, and `false` default.
- [x] `boolSetting("colorizeIcon", true)` returns true; malformed values such
as strings and numbers fall back to false.
- [x] The setting is grouped under General and included in visible settings
without changing existing group ordering or setting semantics.
**Verification:**
- [x] Tests pass: `node tests/setup-settings.test.js`
- [x] Tests pass: `node tests/lock-state.test.js`
- [x] Static check confirms manifest/model schema parity.
**Dependencies:** None
**Files likely touched:**
- `manifest.json`
- `BitwardenModel.js`
- `tests/setup-settings.test.js`
- `tests/lock-state.test.js`
**Estimated scope:** Medium: 3–4 files
### Task 2: Wire the theme-accent shield and General toggle
**Description:** Add the root property that reads `colorizeIcon`, use it only
for the primary shield glyph in the menu-bar icon component, and rely on the
existing schema-driven settings delegate to render and persist the toggle.
Add focused static assertions for the binding and for the unchanged status
badge color paths.
**Acceptance criteria:**
- [x] `colorizeIcon` is read from the live setting with a false-safe default.
- [x] The primary shield resolves to `Color.accent` when enabled and the
existing bar foreground when disabled.
- [x] The padlock, missing-tool badge, and urgent/error color bindings are not
redirected through the new preference.
**Verification:**
- [x] Tests pass: `node tests/settings-screen.test.js`
- [x] Focused source assertions pass for shield color precedence and badge
independence.
- [x] QML lint passes for `Panel.qml` with the repository's Omarchy import path.
**Dependencies:** Task 1
**Files likely touched:**
- `Panel.qml`
- `tests/settings-screen.test.js`
**Estimated scope:** Small: 1–2 files
### Task 3: Add regression coverage and document the setting
**Description:** Complete the feature's regression matrix and user-facing
documentation. Verify that persistence survives the shell reload path and that
the selected accent is inherited from the active theme while status indicators
remain recognizable.
**Acceptance criteria:**
- [x] Tests cover default-off behavior, enabling/disabling through the settings
path, malformed persisted values, and preservation of badge colors.
- [x] User-facing documentation says the toggle follows the active Omarchy
theme accent and does not offer arbitrary color selection.
- [x] No unrelated panel colors or status semantics change.
**Verification:**
- [x] Full JavaScript suite passes:
`for test_file in tests/*.test.js; do node "$test_file" || exit 1; done`
- [x] QML suite passes:
`env -u DISPLAY -u WAYLAND_DISPLAY -u QT_QPA_PLATFORMTHEME QML_XHR_ALLOW_FILE_READ=1 QT_QPA_PLATFORM=offscreen /usr/lib/qt6/bin/qmltestrunner -input tests/qml`
- [x] Plugin validation passes: `omarchy plugin validate .`
- [ ] Manual runtime check confirms both toggle states and theme-derived color
behavior when the desktop environment is available.
**Dependencies:** Task 2
**Files likely touched:**
- `README.md` or the relevant feature documentation
- `tests/setup-settings.test.js`
- `tests/settings-screen.test.js`
- `tests/lock-state.test.js`
**Estimated scope:** Medium: 3–4 files
## Risks and Mitigations
| Risk | Impact | Mitigation |
|---|---|---|
| Accent color has poor contrast on a supported theme | Medium | Check light/dark themes manually; retain the existing bar foreground as the safe default and do not alter urgent badges. |
| The setting is added to one schema but not the other | Medium | Keep manifest/model parity assertions in the focused settings tests. |
| The setting accidentally recolors status badges | High | Limit the binding change to the primary shield `Text` and assert badge bindings remain independent. |
| Shell reload does not refresh the bar icon immediately | Low | Verify the existing `omarchy bar set` hot-reload behavior; do not add a second persistence mechanism. |
## Open Questions
- Confirm final user-facing label: **Colorize menu-bar icon** versus **Use
theme accent for icon**. The plan assumes the former.
- Confirm which user-facing documentation file should receive the short setting
note; `README.md` is the default unless project conventions prefer
`docs/features.md`.