Skip to main content

Module browser_credential_store

Module browser_credential_store 

Source
Expand description

OS-keychain-backed storage for browser-pane HTTP Basic-Auth credentials, scoped per identity so one agent’s saved site credential is never reachable through another identity’s browsing session.

Reuses crate::identity::secret_store’s existing keyring-crate wrapper (already backing SecretRef::Keychain for account API keys) — this module only builds the right account-key string and JSON-encodes the {username, password} pair; no new keychain code, no filesystem path involved (so agentmux_common::data_paths::sanitize_path_segment genuinely doesn’t apply here — nothing is written to disk).

See docs/status/majestic-painting-minsky plan (credential-isolated browser-pane auto-fill) and docs/specs/SPEC_BROWSER_PANE_HTTP_BASIC_AUTH_2026_05_18.md (the v1 spec that named “Persisted credential store (OS keyring)” as deferred future work — this module is that follow-up).

Structs§

BrowserCredential
A saved browser credential. Zeroizes on drop so a decoded value doesn’t linger in memory longer than the caller actually needs it — mirrors secret_store::get’s own Zeroizing<String> discipline for the raw keychain read.

Functions§

account_key 🔒
Build the keychain account string for one (identity, protection-space) pair. Deliberately NOT naive :-concatenation of the raw fields — origin itself contains : (https://host:8443) and realm is server-supplied, effectively attacker-influenced, free text, so two different (origin, realm) pairs could otherwise concatenate to the same string (e.g. origin="https://a:1" + realm="b" vs origin="https://a" + realm="1:b"). Hashing a NUL-separated tuple removes any field-boundary ambiguity — NUL can’t appear in either field in practice, but even if it could, the hash input’s own delimiter collision risk is the same NUL-in-a-field edge case for both operands, which cancels out (unlike :, which is valid content in origin).
delete
Delete the stored credential for this identity + protection-space. Idempotent (a missing entry is success) — same contract as secret_store::delete. Called when a stored credential turns out to be wrong (a re-challenge shortly after an auto-fill — see the credential_broker orchestration in agentmux-cef).
load
Look up a stored credential. Ok(None) means “nothing saved for this identity + protection-space” (not an error) — same contract as secret_store::get_optional, which this wraps directly so a transient keychain failure (locked keychain, no Secret Service daemon, permission denied) is distinguishable from “genuinely never saved” at the call site, the same distinction secret_store’s own doc comment argues for.
save
Store (or overwrite) username/password for this identity + protection-space. Unbounded (no timeout) — see secret_store’s doc comment for why a write must not race a timed-out-but-later-completing keychain call.