feat: support env:${VAR} references in apiKey field#6
Conversation
✅ Rotation verification (env:VAR resolution)Tested with 3 opencode-go API keys using Key resolutionFull rotation cycleWith explicit envVarName{
"apiKey": "env:OPENCODE_API_KEY_1",
"envVarName": "OPENCODE_API_KEY"
}→ |
🔥 Additional fixes found during end-to-end testingBug 1: auth.json format —
|
🐌 Root cause of response stalling foundProblem: Every API request triggered synchronous file I/O:
On every single request: 3 file reads + 1 file write = synchronous blocking I/O. Fixed:
|
- resolveValueRef(): resolves apiKey values prefixed with `env:` from process.env at runtime (normalizeAccount, resolveAccountEnv) - auth.json: write `key` instead of `access` for api auth type (buildAuthJsonEntry, overwriteAuthJsonProvider, _findAccountBySecret) - Remove console.log calls from account-manager.ts (stdout pollution)
12f49a9 to
693c6ca
Compare
PR cleaned upThis PR now contains only the essential, non-controversial fixes:
The aggressive interceptor, watchdog, and I/O changes have been removed from this PR — those were local tuning. |
Problem
The
accounts.jsonconfig stores API keys in plaintext. Users who manage secrets via environment variables or a vault system (e.g. Drewgent vault, 1password CLI,pass, etc.) must either:accounts.jsondynamically (extra complexity)Solution
Add a
resolveValueRef()helper that detects theenv:prefix in theapiKeyfield and resolves the value fromprocess.envat runtime.Example
accounts.json:{ "accounts": [ { "id": "my-p1", "provider": "anthropic", "apiKey": "env:ANTHROPIC_API_KEY_1", "priority": 1, "cooldownPeriod": 300000 } ] }Implementation
resolveValueRef(value)— if value starts withenv:, reads the rest as an env var name and returnsprocess.env[varName]. Falls back to the literal value if the env var is not set (makes missing vars visible via auth errors).normalizeAccount()— resolvesaccount.apiKeybefore buildingcredentials.env, so the stored env map always contains the actual secret.resolveAccountEnv()— resolves all values incredentials.envbefore returning, and resolvesaccount.apiKeyin the legacy path. This ensures every consumer ofresolveAccountEnv()— credential validation, shell/env injection, HTTP headers, and auth.json sync — gets the resolved value.Testing
env:EXISTING_VAR→ resolves toprocess.env[EXISTING_VAR]env:MISSING_VAR→ returns literalenv:MISSING_VAR(fail-open for debugging)sk-...(plaintext) → passes through unchangedenvVarNamefield for providers where the env var name differs from the inferred default.Security
No plaintext secrets in config files. Keys are resolved at runtime from the process environment, which can be populated from any secret store (vault, keychain, 1password, etc.).