Secrets & Keychain

In the desktop build, API tokens are held in the OS keychain and resolved inside Rust. They are never written to instances.json, never written to localStorage, and never cross the webview boundary on a request.

How a request gets its token

  1. The webview calls invoke('dataplane_fetch', {...}) — an IPC message, not a network request.

  2. Rust reads the token from the keychain, keyed by instance id (instance:{id}).

  3. Rust makes the HTTP request via reqwest, with the token attached.

  4. The response goes back to the webview. The token does not.

This is what makes the desktop build trustworthy with a production bearer token: there is no code path by which the token reaches the webview, so an XSS in the UI layer cannot exfiltrate it.

Storage per platform

OS

Backend

macOS

Keychain (Security.framework)

Windows

Credential Manager

Linux

Secret Service (gnome-keyring, KWallet)

First-save prompts

The OS prompts once per host when HAProxy UI first writes a secret. What to expect:

  • macOS — a one-time “Always Allow / Deny” dialog from Security.framework. It is non-blocking but visible, so foreground the Tauri window before saving the first token. Choose Always Allow or every subsequent save re-prompts.

  • Windows — Credential Manager is silent on a normal save. A consent dialog appears only under stricter Group Policy.

  • Linux — requires a running Secret Service. On a headless machine or a fresh box with no desktop session, every read and write fails until one is started. The usual fix is dbus-launch --exit-with-session gnome-keyring-daemon.

Migration from older versions

An instances.json written before keychain support may contain a plain-token field. On first read it is migrated to the keychain automatically, and the next save scrubs it from disk. No manual step, and no window in which a token sits in both places.

Browser mode is different

The browser build has no keychain available. There, secrets fall back to localStorage — which is browser-readable by anything running on the origin. This is a real limitation, not an oversight.

Treat the browser build as suitable for the mock fixture, the Vite proxy against a local development stack, or a Dataplane you control. For a production bearer token, use the desktop build. The API token is the credential; a web UI that stores it in localStorage is not the right place for it.

What is stored where

Data

Location (desktop)

Contains secrets?

API tokens

OS keychain, keyed instance:{id}

Yes — and only here

Instance list (URL, label, auth scheme, mode)

instances.json in the app data dir

No

Working draft

localStorage, keyed by instance id

No

Config snapshots

App data dir

No — but note snapshots contain full configuration, including anything sensitive in it

SSH keys for GitOps

GitOps SSH authentication uses ssh-agent from the Rust side. Private key files are never read into the webview and never copied into the app data directory.

Updater signing key

Separate from instance credentials: the Tauri updater uses an Ed25519 keypair. The public half is committed in src-tauri/tauri.conf.json; the private half lives only on the maintainer’s machine and in a CI secret. Rotating it requires a two-key transition release — see Installation & Releases.


Was this article helpful?