Skip to content

fix(auth): cache keyring probe and rename misleading encrypt/decrypt#106

Merged
tjb-tech merged 1 commit intoHKUDS:mainfrom
glitch-ux:fix/auth-storage-keyring-noise-and-misleading-crypto
Apr 11, 2026
Merged

fix(auth): cache keyring probe and rename misleading encrypt/decrypt#106
tjb-tech merged 1 commit intoHKUDS:mainfrom
glitch-ux:fix/auth-storage-keyring-noise-and-misleading-crypto

Conversation

@glitch-ux
Copy link
Copy Markdown
Contributor

@glitch-ux glitch-ux commented Apr 11, 2026

Problem

1. Keyring warning spam floods stderr

On systems without a desktop keyring backend (WSL, containers, CI, headless Linux), every single credential operation calls _keyring_available() → imports keyring → tries to use it → fails → logs a warning. A single oh setup gemini produces 22 identical warnings that drown out the actual output:

$ oh setup gemini
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Google Gemini requires Gemini API key.
Enter API key for Google Gemini:

That is the full, unedited output of a single command. Every store_credential / load_credential / clear_provider_credentials call independently probes the keyring, fails, and prints. This affects every user on WSL, headless Linux, containers, and CI — the most common non-macOS environments.

oh auth login gemini produces a similar wall:

$ oh auth login gemini
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Keyring load failed, falling back to file: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details.
Enter your Google Gemini API key:

2. encrypt/decrypt are XOR obfuscation, not encryption

encrypt() and decrypt() in auth.storage are XOR with a key deterministically derived from Path.home() — trivially reversible by anyone with filesystem access. Naming them encrypt/decrypt and exporting them in __all__ misleads callers (and auditors) into thinking credentials have cryptographic protection. They are not called anywhere in the codebase today, but the public API surface invites misuse.

Fix

Keyring caching

  • _keyring_available() now probes the backend once on first call, caches the result in a module-level flag, and returns the cached value on subsequent calls
  • The probe does an actual keyring.get_password() round-trip instead of just checking the import — this catches the common case where keyring is installed as a transitive dep but no backend is configured
  • On failure, logs a single INFO-level message instead of a WARNING per call

Rename obfuscation helpers

  • encrypt_obfuscate (private)
  • decrypt_deobfuscate (private)
  • Module docstring updated to document the actual security model (file permissions, not crypto)
  • encrypt/decrypt kept as deprecated aliases for backward compatibility
  • __init__.py exports annotated as deprecated

Test plan

  • ruff check src tests scripts passes
  • Full test suite passes (573 passed, 6 skipped, 1 xfailed — identical to main)
  • _obfuscate / _deobfuscate round-trip verified
  • Deprecated encrypt / decrypt aliases still work (point to same functions)
  • Keyring probe runs only once per process (verified via module-level cache)

…helpers

Two issues in auth/storage.py:

1. Every call to store_credential / load_credential / clear_provider_credentials
   probes the system keyring. When no backend is available (WSL, containers, CI),
   each probe fails and logs a warning. A single `oh setup` invocation triggers
   20+ "Keyring load failed" warnings because the availability is never cached.

   Fix: probe once on first call, cache the result, and log at INFO level
   instead of WARNING so users see a single quiet message.

2. The public `encrypt` / `decrypt` functions are XOR obfuscation with a key
   derived from the home-directory path — trivially reversible by anyone with
   filesystem access. Naming them encrypt/decrypt misleads callers into
   thinking credentials are protected by real cryptography.

   Fix: rename to `_obfuscate` / `_deobfuscate` (private), update module
   docstring to document the security model, and keep `encrypt` / `decrypt`
   as deprecated aliases for backward compatibility.
@tjb-tech tjb-tech merged commit 7209b69 into HKUDS:main Apr 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants