Skip to content

feat(credentials): optional OS-keychain read fallback for CredentialStore - #246

Merged
Zongwei9888 merged 1 commit into
HKUDS:mainfrom
raymondginger2018-sudo:rework/keyring-store
Sep 27, 2026
Merged

Zongwei9888 merged 1 commit into
HKUDS:mainfrom
raymondginger2018-sudo:rework/keyring-store

Conversation

@raymondginger2018-sudo

Copy link
Copy Markdown
Contributor

Description

Adds an opt-in OS-keychain read fallback to CredentialStore, in the shape suggested when #188 was closed: the existing resolution order (env → credential store → legacy config) gains keychain support without a second store. keyring stays an undeclared optional import, and the JSON credential file remains the source of truth and the only write target.

Related Issues

Refs #188 (closed) — reworked per review.

Changes Made

  • core/providers/credentials.py (+49): KEYRING_ENV_VAR = "DEEPCODE_KEYRING" (truthy spellings 1/true/yes/on) gates a lazily imported keyring. CredentialStore.get() falls back to keyring.get_password(KEYRING_SERVICE, connection_id) only when the file has no value for that connection.
  • Every failure path degrades to today's behavior: package missing, no usable backend, locked/denied keychain, a non-str return — all return None, so the resolver falls through to legacy_config. Turning the knob on cannot break a working setup.
  • Writes are untouched: set/clear never touch the keychain, which keeps revision's mtime/size fingerprint meaningful and leaves ownership of the secret exactly where it is today.
  • tests/test_provider_credentials.py (+216, 10 tests): not consulted by default; falsey knob spellings; keychain supplies an absent credential; the file still wins when both exist; missing package degrades; backend failure never escapes; non-string file value falls through; the store never writes to the keychain; a keychain-only credential leaves no revision trace; end-to-end resolution order.

Checklist

  • Changes tested locally (10 new tests, plus the runtime suite; ruff check + ruff format --check clean)
  • Unit tests added (if applicable)

Additional Notes

  • No new dependency, no write path, and behavior is byte-identical when DEEPCODE_KEYRING is unset.
  • A keychain write path (set storing into the keychain) is deliberately out of scope: it needs a decision on who owns the secret and on how revision tracks it. Happy to follow up separately if that is wanted.

The credential store resolves from a 0600 JSON file only, so users who keep
API keys in the OS keychain (Windows Credential Manager, macOS Keychain, the
Linux Secret Service) have to duplicate them into a plaintext file.

Add a read-only keychain fallback behind the DEEPCODE_KEYRING knob, consulted
only when the file holds no value for a connection. keyring stays an optional
import: the package is not a declared dependency, so a default install is
unaffected and every import/backend failure degrades to the file. The file
remains the source of truth and the only write target -- set/clear never touch
the keychain -- which keeps revision()'s mtime/size fingerprint meaningful.

The resolver chain is unchanged: a keychain-backed credential surfaces as
credential_source="credential_store", because the keychain is that store's
backend, not a fifth tier.

(cherry picked from commit 6466dbd187814623029fc55521f391df3b930da4)
@Zongwei9888
Zongwei9888 merged commit cc2eaec into HKUDS:main Sep 27, 2026
12 checks passed
Zongwei9888 added a commit that referenced this pull request Sep 27, 2026
…entialStore

With DEEPCODE_KEYRING=1 and the optional keyring package, CredentialStore.get
falls back to the OS keychain (service deepcode, user = connection id) when the
credential file has no key; writes still go only to the file and every keychain
failure degrades to the previous behaviour. Repair: document the setup.
Contributed by raymondginger2018-sudo.
@Zongwei9888

Copy link
Copy Markdown
Collaborator

Merged into main as 14aad77. Thank you @raymondginger2018-sudo — this is the shape suggested when #188 was closed: the file stays the source of truth and the only write target, the keychain is read only when the file has nothing, and every keychain failure degrades to today's behaviour. The ten tests cover exactly the properties that matter.

One addition on our side so the feature is discoverable: docs/HEADLESS_AND_AUTOMATION.md now shows how to enable it (pip install keyring, DEEPCODE_KEYRING=1, keyring set deepcode <connection-id>) and states that DeepCode never writes to the keychain. A write path can be a separate discussion, as you suggested.

Zongwei9888 added a commit that referenced this pull request Sep 28, 2026
…guide

The English half of docs/HEADLESS_AND_AUTOMATION.md gained the optional OS
keychain setup (#246) and the mcp list CONCERNS/posture note (#241); the Chinese
half now says the same.
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