Repository navigation
Settings store read-only for the webview; UI preferences written through one validated backend command #862
Copy link
Copy link
Open
Labels
Milestone
Description
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 ofsettings.jsondirectly, including the security keys; this issue closes that. - Capabilities:
apps/smart-ssh-community/capabilities/default.jsonandmobile.jsongrant"store:default"(includes set/delete/clear/reset/save). - Remaining direct frontend writes to the store (
settingsStore()fromsrc/i18n.ts:36):src/i18n.ts:140language(read by the backend:crates/app-shell/src/ui_language.rs:19)src/firstRunNotice.ts:21first_run_notice_acknowledged(backend:first_run_notice.rs:11)src/voiceInputSettings.ts:23voiceInputEnabledsrc/fileTypeSettings.ts:54fileTypeDefaultAppssrc/layoutSettings.tsfileManagerColumnWidths,aiSshSplitWidthPx,fileManagerHiddenColumns, … (frontend-only keys)src/autoReconnectSettings.ts:30-31autoReconnectSeconds(backend:app_logic::auto_reconnect::SETTING_KEY)
Re-check withgrep -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)(anddelete_ui_preference(key)if a current caller deletes) incrates/app-shell, writing throughsettings_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:
Wiringgets 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 tosettingsStore()); reads stay on the store. - Both capability files replace
store:defaultwith 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.jsonandmobile.jsoncontain 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 containingstore:allow-set. - Rust tests:
set_ui_preferenceaccepts 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
Wiringmakes 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.resetorstore.save(guard test with counter-check); the existing tests of the migrated settings modules pass with the store write mocked as aninvoke. - 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.mdandTHREAT-MODEL.md. Review priority elevated. - No data migration: keys and JSON types stay the same.
- The
Wiringfield 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
- Threat model: state that the app's webview is part of the trusted base #818 (threat model: webview is part of the trusted base)
- Write settings.json through one helper that keeps memory and file consistent on failure #265
- Blocked by Security settings (red-risk confirmation, risk second opinion) are written only through backend commands #856 (risk settings through backend commands)
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
P1 - High
Activity
19 remaining items