Skip to content

Store third-party provider API keys in the OS keychain (currently plaintext in config.json) #1221

Closed

Description

@iifuaann-zhu

What are you trying to accomplish?

When using opencodex with API-key providers such as DeepSeek, I'd like the raw key material to not sit in plaintext JSON on disk.

What prevents this today?

providers.<name>.apiKey and apiKeyPool are persisted verbatim in ~/.opencodex/config.json (and in config backups). File permissions are hardened to 0600 and atomic writes scrub temp-file residuals, but the key material is still recoverable from the config file, backups, or any accidental copy or screenshot — unlike native GPT main profiles, which PR #863 already stores encrypted (no plaintext fallback).

What should OpenCodex do?

Provide an opt-in, OS-keychain-backed credential store for provider API keys — e.g. macOS Keychain (security find-generic-password), Windows Credential Manager/DPAPI, Linux libsecret — enabled by a per-provider flag such as credentialStore: "keychain". When the keychain is unavailable or the user opts out, fall back to the current 0600-permission config-file behavior.

Example usage or interface

ocx config set providers.deepseek.credentialStore keychain

The key would be written to the OS keychain, and config.json would hold only a reference (e.g. keychain:deepseek) instead of the raw key.

Alternatives or workarounds

Env-var references already work today: set providers.deepseek.apiKey to ${DEEPSEEK_API_KEY} and export the variable in the service environment (resolved at routing time). This is the current mitigation; keychain support would remove the need to manage service env vars and would also cover apiKeyPool entries.

Additional context

Verified on opencodex 2.10.2 (macOS). Native profiles already use an encrypted store (PR #863), so this extends an existing pattern to key-auth providers.

Checks

  • I searched existing issues and documentation.
  • This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
  • I removed secrets and personal data.

Activity

  1. github-actions commented on Aug 7, 2026

    @github-actions
    Contributor

    Automated translation bookkeeping — detected language: unknown.

  2. github-actions commented on Aug 7, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  3. assigned and unassigned on Aug 9, 2026
  4. lidge-jun commented on Aug 19, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 61 / 80

    현재 dev에서 서드파티 프로바이더 키는 그대로 ~/.opencodex/config.json에 남습니다. src/providers/api-keys.ts 주석도 apiKeyPool이 apiKey와 같은 파일에 산다고 적고, src/types.ts의 apiKeyPool 엔트리는 { id, key, label, addedAt }입니다. src/cli/provider.ts는 출력 때만 maskSecret을 쓰고, 저장 경로를 바꾸지는 않습니다. 파일 권한 0600과 원자적 쓰기는 있어도 평문 잔존은 맞습니다.

    이슈가 원하는 것은 프로바이더별 credentialStore: "keychain" 같은 선택 저장소입니다. 키 본체는 OS 키체인(macOS Keychain, Windows Credential Manager/DPAPI, Linux libsecret)에 두고, config.json에는 keychain:deepseek 같은 참조만 남기자는 요청입니다. 네이티브 GPT 프로필은 이미 암호화 저장(PR #863, cli_auth_credentials_store)이 있으므로, 키 인증 프로바이더에 같은 패턴을 확장하자는 취지입니다.

    코드상 키체인 흔적은 Claude 쪽 탐지에 더 가깝습니다. src/claude/auth-detect.ts의 macos-keychain 프로브는 메타데이터만 읽고 -g/-w를 쓰지 않으며, 거부된 키체인을 구독 실패로 오인하지 않게 막습니다. src/codex/native-profile-store.ts는 cli_auth_credentials_store를 읽습니다. 둘 다 프로바이더 apiKey/apiKeyPool을 OS 키체인에 쓰는 구현이 아닙니다. credentialStore 필드도 현재 프로바이더 스키마에서 검색되지 않습니다.

    Windows/서비스 경로가 이 이슈의 실제 난이도입니다. 데스크톱에서 키체인에 넣어도 launchd/서비스/헤드리스 프로세스는 같은 시크릿 공간에 접근하지 못할 수 있고, 그때 폴백이 평문 재기록이면 의미가 줄어듭니다. 이미 있는 ${DEEPSEEK_API_KEY} env 참조는 라우팅 시점에 해석되므로, 서비스 환경에서는 키체인보다 env가 더 안정적일 수 있습니다.

    해결방안은 옵트인 한 프로바이더부터, 읽기 경로를 resolveProviderApiKey() 한곳으로 모은 뒤 키체인 참조를 해석하는 것입니다. 키체인 불가·거부·서비스 세션 부재 시 현재 0600 파일 저장으로 되돌리고, 백업 파일에 평문이 다시 쓰이지 않게 백업 직렬화도 같은 해석기를 타야 합니다. 패치는 추측하지 않으며, 현재 트리에 프로바이더 키체인 저장 구현은 없습니다.

    이 댓글은 grok-bot이 작성했습니다

  5. lidge-jun commented on Sep 1, 2026

    @lidge-jun
    Owner

    Landed on dev via #3210 (squash 941cb45b6).

    What shipped:

    • ocx provider keychain <name> store (or POST /api/providers/keychain) moves the provider's active key and every pool entry into the OS credential store (macOS Keychain, Windows Credential Manager, Linux Secret Service, via @napi-rs/keyring) and rewrites config.json with keychain:<provider>[/<id>] references, so the file and its backups carry no key material. restore reverses it; status reports where the key lives and whether the keychain answers.
    • Every request-time read of apiKey now goes through one resolver, as the review asked, so env references, keychain references, and literal keys behave the same across routing, quota, compaction, and discovery. Key failover persists references, never plaintext.
    • Safety: store verifies each keychain write by read-back before touching config and refuses with 503 when the store is unavailable or lies. At request time an unreadable reference yields no credential with one warning; there is deliberately no plaintext fallback.

    The headless-service caveat from the review is documented: a proxy under systemd/launchd/Task Scheduler usually has no unlocked keychain session, so ${ENV_VAR} references stay the right choice there, and store leaves env references untouched. A dashboard control for the same operation is a reasonable follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions