Skip to content

Security and Privacy

Saco Song edited this page Jul 31, 2026 · 6 revisions

Security and Privacy

简体中文 · Home

This page describes the current implementation, not a security certification. Review provider policies and your local desktop threat model before enabling remote features.

Data-flow summary

Feature Data leaving the machine
local-cli ASR Voice Input itself makes no remote ASR request. It passes a temporary WAV path to /usr/bin/voxtype; that separate backend's behavior depends on its own configuration.
Qwen realtime ASR PCM16 audio chunks, language/session options, and model selection go to the configured Alibaba WebSocket endpoint.
Qwen final pass The complete temporary WAV is embedded as a base64 data:audio/wav request to the configured Alibaba-compatible HTTP endpoint.
LLM refinement Transcript text goes to the configured OpenAI-compatible /chat/completions endpoint.
Agent context Only when enabled, a redacted/capped latest assistant-message excerpt and Pi/Codex label accompany the transcript.
Quickshell HUD / output Local runtime state, Unix socket data, clipboard commands, and input simulation only.
Quickshell Settings Local versioned NDJSON to a dedicated Rust child. Only an explicit Test LLM request contacts the configured LLM endpoint.

The current source contains no telemetry or analytics client. Network traffic is used by the configured remote ASR and LLM adapters.

Microphone lifetime

With the public default:

[audio]
pre_roll_enabled = false

pw-record starts for a dictation session and is terminated on stop/cancel. When pre-roll is enabled, a resident capture service keeps the microphone stream open for the daemon's lifetime, retaining a short in-memory ring buffer. This reduces clipped first syllables but changes the privacy model and may keep the desktop microphone indicator active.

The complete active-session audio is also retained in memory until final processing finishes. Local and full-audio final recognition use temporary WAV files managed by the operating-system temporary directory; Voice Input does not intentionally archive recordings.

Transcript persistence

The runtime snapshot contains the live transcript and, after output, may retain:

  • raw ASR text;
  • refined text;
  • refinement status;
  • output target/mode/driver;
  • the effective recording start timestamp while recording and frozen duration afterward;
  • a shortened transcript in the tooltip.

It is written to:

$XDG_RUNTIME_DIR/voice-input/state.json

The waveform publisher sets the runtime directory to mode 0700 and waveform.sock to 0600. Runtime data normally disappears with the user session. A custom state_file creates an additional persistent copy; choose a private path and permissions, or keep auto. voice-input status --format json --extended prints these transcript fields, so sanitize captured command output before sharing it.

Configuration persistence

Settings QML cannot write configuration. Its Rust child exclusively loads, validates, and serializes the complete supported config. Rust binds each load to an opaque exact-source revision and rejects a save if the file changed after that load. Successful writes use a mode-0700 configuration directory, a mode-0600 file, and temporary-file atomic replacement. This prevents partial files and reduces accidental concurrent overwrites; it does not replace backups.

Encrypted credentials

The standard services declare two optional encrypted credential IDs:

alibaba-api-key
openrouter-api-key

Settings writes encrypted blobs under:

${XDG_CONFIG_HOME:-$HOME/.config}/credstore.encrypted/

Use the Settings UI to avoid putting a literal key in shell history:

voice-input settings

A newly entered key is a password input. QML sends it only through the dedicated child's inherited stdin; Rust then sends it through stdin to systemd-creds encrypt --user --name=<id>. It is never placed in TOML, process arguments, environment variables, logs, or a backend response. Backend responses expose only non-secret credential metadata. The encrypted-store directory is mode 0700; each blob is mode 0600 and is installed by atomic replacement.

Credential operations in this UI are limited to keep and replace. Blank means keep; the UI does not offer deletion. Save and Test LLM clear entered credential fields as soon as the request has been submitted. QML/JavaScript strings are managed values, so clearing references is best effort and cannot guarantee immediate memory zeroization. Test LLM can use the newly entered LLM key without storing it, or use the existing encrypted-store credential.

At service launch, systemd decrypts selected credentials into $CREDENTIALS_DIRECTORY; the daemon reads them from there. Missing credentials are compatible with local-only use. Protecting encrypted blobs still matters; systemd credential security depends on the host and user credential mechanisms available on that system.

The credential named openrouter-api-key is also used for a different OpenAI-compatible api_base_url. The ID is an implementation name, not provider verification.

Resolution precedence

The daemon resolves each key at startup in this order:

  1. systemd $CREDENTIALS_DIRECTORY;
  2. VOICE_INPUT_ALIBABA_API_KEY or VOICE_INPUT_OPENROUTER_API_KEY;
  3. legacy TOML value accepted from an older configuration.

The public sample has no key fields, and serialization deliberately omits runtime key values. Avoid legacy plaintext TOML and environment variables for the supplied long-running service. Environment variables can be visible through same-user process inspection, diagnostics, or service configuration.

Agent-context safeguards and limits

Voice Input binds context to the Kitty Pi/Codex process focused when dictation ends and validates PID start time, canonical session root, session identity, file device, and inode. It reads at most 8 MiB from the session tail, chooses only a completed assistant message, removes known secret lines/token patterns, and clamps the outgoing excerpt to 500–12,000 characters.

The LLM prompt marks session text as untrusted and terminology-only. These controls reduce accidental disclosure and prompt injection; heuristic redaction cannot identify every secret, personal detail, proprietary sentence, or novel injection. Keep agent_context_enabled = false for sensitive sessions or untrusted providers. Full details are in Agent Context.

Clipboard and synthetic input

Paste mode temporarily replaces the Wayland or X11 clipboard, sends a paste chord, waits 220 ms, and restores the captured content. During that interval, another same-user clipboard client can observe the transcript. Backup can also be unavailable or restoration can fail, especially for unsupported MIME types or a changing clipboard owner.

clipboard mode intentionally leaves transcript text in the clipboard. Direct type uses wtype and avoids storing the transcript there, but text over 120 characters and default XWayland routing use paste automatically.

Logs

Daemon logs include operational metadata such as:

  • ASR event/sample counts and first-partial timing;
  • LLM attempt type, elapsed milliseconds, coarse outcome, and whether ASR text was retained;
  • output target, effective mode, and driver;
  • agent type and sanitized excerpt character count.

The timing paths intentionally do not print transcript, agent excerpt, or credentials. Settings also suppresses arbitrary child stderr and never returns credential values. Some propagated backend/provider error messages may originate outside Voice Input; review them before posting.

Privacy-safe diagnostic collection:

journalctl --user -u voice-input.service -b \
  | grep -E 'voice-input (realtime ASR|refinement|output|agent context)'

Inspect the result manually and remove user names, paths, endpoint details, provider errors, and any unexpected content before sharing.

Practical hardening checklist

  • Leave pre-roll, remote ASR, final pass, LLM, and agent context disabled until needed.
  • Use encrypted systemd credentials and retain 0700 config-directory / 0600 config-file permissions.
  • Keep the explicit /usr/bin/voxtype path and audit that backend separately.
  • Restrict custom state paths; do not persist state into a synchronized/public directory.
  • Treat clipboard managers and same-user processes as able to observe paste payloads.
  • Use trusted HTTPS/WSS endpoints and models; endpoint fields are user-controlled.
  • Review journalctl and extended status output before attaching them to an issue.

See also: Configuration · Troubleshooting

Clone this wiki locally