Skip to content

Settings store read-only for the webview; UI preferences written through one validated backend command #862

Open

Description

@smart-ssh-planner

Goal

The webview can read the settings store but cannot write it. Every settings change goes through a backend command: security settings through their own commands, plain UI preferences through one generic command that only accepts registered preference keys.

Context

  • Decided by maintainer: security switches are changeable only through backend commands (Security settings (red-risk confirmation, risk second opinion) are written only through backend commands #856 adds them for the risk settings). As long as the capability grants store:default, the webview can still write any key of settings.json directly, including the security keys; this issue closes that.
  • Capabilities: apps/smart-ssh-community/capabilities/default.json and mobile.json grant "store:default" (includes set/delete/clear/reset/save).
  • Remaining direct frontend writes to the store (settingsStore() from src/i18n.ts:36):
    • src/i18n.ts:140 language (read by the backend: crates/app-shell/src/ui_language.rs:19)
    • src/firstRunNotice.ts:21 first_run_notice_acknowledged (backend: first_run_notice.rs:11)
    • src/voiceInputSettings.ts:23 voiceInputEnabled
    • src/fileTypeSettings.ts:54 fileTypeDefaultApps
    • src/layoutSettings.ts fileManagerColumnWidths, aiSshSplitWidthPx, fileManagerHiddenColumns, … (frontend-only keys)
    • src/autoReconnectSettings.ts:30-31 autoReconnectSeconds (backend: app_logic::auto_reconnect::SETTING_KEY)
      Re-check with grep -rn "store\.\(set\|delete\|clear\|reset\|save\)(" src --include=*.ts --include=*.tsx (non-test files).
  • settings_store::update (crates/app-shell/src/settings_store.rs:122) is the one write helper (issue Write settings.json through one helper that keeps memory and file consistent on failure #265).
  • An embedding app crate can add its own frontend modules that keep UI preferences in the same store; it configures the core through Wiring (crates/app-shell/src/wiring.rs:30).

Requirements

  • New command set_ui_preference(key: String, value: serde_json::Value) (and delete_ui_preference(key) if a current caller deletes) in crates/app-shell, writing through settings_store::update. It accepts only keys from a UI preference registry; each entry declares the allowed JSON shape (bool, integer range, string with max length, or JSON with max serialised size). Unknown keys and shape violations are rejected with an error code.
  • The registry contains the keys listed above (and any other current non-security frontend key found by the grep). It never contains a security key (redRiskAlwaysConfirm, riskClassifierEnabled, riskClassifierProviderId, mcpServer*, localServerTags).
  • Extension point: Wiring gets a field to register additional UI preference keys (with their shape) at startup, default empty, so an embedding app can keep its own preferences in the store. Registering a security key or a key already registered fails at startup.
  • All frontend writers above use set_ui_preference (a small helper next to settingsStore()); reads stay on the store.
  • Both capability files replace store:default with read-only store permissions (e.g. store:allow-load, store:allow-get-store, store:allow-get, store:allow-has, store:allow-keys, store:allow-values, store:allow-entries, store:allow-length, store:allow-reload; check the plugin's permission list for the exact set). Verify that the frontend still sees values written by the backend (same store instance by path).
  • THREAT-MODEL.md: in the trusted-base subsection and the §6 residual-risk bullet added by Threat model: state that the app's webview is part of the trusted base #818 (add them if Threat model: state that the app's webview is part of the trusted base #818 is not merged yet), state that the webview can read but not write the settings store and that security settings change only through backend commands; the "planned hardening" wording becomes a statement of the current state.
  • Comments in new code in English.

Acceptance Criteria

  • Capability test (Rust or script, run in CI): default.json and mobile.json contain no store permission that can write (store:default, store:allow-set, store:allow-delete, store:allow-clear, store:allow-reset, store:allow-save); counter-check with a sample containing store:allow-set.
  • Rust tests: set_ui_preference accepts each registered key with a valid value; rejects an unknown key, every security key, and a value of the wrong shape/size, leaving the file unchanged.
  • Rust test: registering an extra key through Wiring makes it writable; registering a security key or a duplicate fails at startup.
  • Frontend: no non-test file calls store.set, store.delete, store.clear, store.reset or store.save (guard test with counter-check); the existing tests of the migrated settings modules pass with the store write mocked as an invoke.
  • Real-app or e2e check: changing the UI language and the auto-reconnect value in the settings screen still persists across a restart.
  • scripts/check-tauri-commands.mjs, locale parity and lint pass.
  • Relevant automated tests pass.

Technical Notes

  • Security-critical area (capabilities, settings that control confirmation): read .claude/skills/smart-ssh-coder/references/security-invariants.md and THREAT-MODEL.md. Review priority elevated.
  • No data migration: keys and JSON types stay the same.
  • The Wiring field is additive with an empty default; the community wiring registers nothing extra.

Spec impact: update 0024 (§4: where UI settings are written), update 0053 (layout preferences written through the backend)

Related Work

Activity

  1. added this to the 0.8.0 milestone on Oct 26, 2026
  2. 19 remaining items

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Fields

    Priority

    P1 - High

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions