Skip to content

feat(config): api_key_helper — dynamic API keys from a shell command#236

Merged
emal-avala merged 1 commit intomainfrom
feat/config-api-key-helper
Apr 24, 2026
Merged

feat(config): api_key_helper — dynamic API keys from a shell command#236
emal-avala merged 1 commit intomainfrom
feat/config-api-key-helper

Conversation

@emal-avala
Copy link
Copy Markdown
Member

@emal-avala emal-avala commented Apr 23, 2026

Summary

Adds api.api_key_helper: an optional shell command whose trimmed stdout becomes the session API key. Intended for sourcing short-lived tokens from a secrets manager (vault, 1Password, aws sts, gcloud auth print-access-token, …) without pinning a long-lived key to disk.

Resolution order (unchanged behavior when helper is unset)

# Source Notes
1 CLI flags / env vars Always wins
2 config.toml api.api_key Literal key
3 api.api_key_helper stdout New — only if 1 and 2 produced nothing
4 Operator-visible "no key" error as before

Configuration example

[api]
base_url = "https://api.example.com/v1"
model = "gpt-5"
# Either a literal key …
# api_key = "sk-…"
# … or a command that prints one:
api_key_helper = "op read op://dev/openai/api-key"

The helper is executed via bash -c, so pipelines and env interpolation work out of the box.

Guarantees

  • Not serialized back outskip_serializing, same as api_key, so dumped configs can't leak it.
  • Leak-safe diagnostics — helper errors are mapped to a coarse ApiKeyHelperError category (SpawnFailed / NonZeroExit / InvalidUtf8). The warn! log only emits the category string, never raw stdout or stderr (either of which could contain the API key itself).
  • Graceful failure — spawn errors, non-zero exits, and invalid UTF-8 leave the key unset. The session proceeds to the existing "no API key" surface instead of aborting load.
  • Trim semantics — only edges are trimmed; a newline inside the secret (rare but legal for some encoded tokens) is preserved.

Tests (10)

Schema (3):

  • Default is None
  • Parses from TOML
  • Not serialized (leak guard)

Runtime (7, unix-only since they shell out to bash):

  • echo result is trimmed
  • Non-zero exit categorized as NonZeroExit
  • Stderr text never appears in the logged category (direct leak regression)
  • Empty stdout resolves to empty string (caller treats as "no key")
  • Multiline output trimmed only at edges
  • Missing inner command maps to NonZeroExit
  • Full category() mapping guard for future variants

Full config:: suite: 107 pass, 0 fail. Clippy clean.

Test plan

  • cargo test -p agent-code-lib --lib config
  • cargo clippy --workspace --tests --no-deps -- -D warnings
  • cargo fmt --all --check

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Comment thread crates/lib/src/config/mod.rs Fixed
@emal-avala emal-avala force-pushed the feat/config-api-key-helper branch from 527a513 to 6a27382 Compare April 23, 2026 23:49
Adds `api.api_key_helper`: a shell command that prints an API key to
stdout. When no static key has been resolved from file or env, the
helper is executed via `bash -c` and its trimmed stdout becomes the
session key.

Use case: pull a short-lived key from a secrets manager (vault,
1Password, `aws sts`, `gcloud auth print-access-token`, …) instead
of pinning a long-lived key to disk.

Resolution order:
1. CLI / env vars (highest)
2. `config.toml` `api.api_key`
3. **`api.api_key_helper` output** ← new fallback
4. None → operator error surfaces as before

The helper field is marked `skip_serializing` (same contract as
`api_key`) so it never lands in a dumped config.

Leak-safe diagnostics: helper errors are categorized via a
private `ApiKeyHelperError` enum (`SpawnFailed` / `NonZeroExit` /
`InvalidUtf8`). Only the category string is logged — never raw
subprocess stdout or stderr, either of which could carry the key.

Tests:
- default is None; parses from TOML; not serialized
- trims trailing newline from stdout
- non-zero exit is categorized as NonZeroExit
- stderr contents never appear in the logged category
- multi-line output trimmed only at edges
- full category-mapping coverage guard
@emal-avala emal-avala force-pushed the feat/config-api-key-helper branch from 6a27382 to 75c672a Compare April 23, 2026 23:56
@emal-avala emal-avala merged commit 0ef8ea5 into main Apr 24, 2026
14 checks passed
@emal-avala emal-avala deleted the feat/config-api-key-helper branch April 24, 2026 04:33
@emal-avala emal-avala mentioned this pull request Apr 24, 2026
4 tasks
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