feat(secrets): AES-GCM encrypted upstream api_keys + grob secrets CLI#275
Merged
Destynova2 merged 1 commit intomainfrom Apr 25, 2026
Merged
feat(secrets): AES-GCM encrypted upstream api_keys + grob secrets CLI#275Destynova2 merged 1 commit intomainfrom
Destynova2 merged 1 commit intomainfrom
Conversation
Adds an encrypted-at-rest store for upstream provider API keys, reusing
the existing StorageCipher (AES-256-GCM) used for OAuth tokens. The
cleartext key never touches config.toml, shell history, or dotfiles.
Three modes for [[providers]] api_key (resolved at startup):
- "secret:<name>" → looked up in ~/.grob/secrets/<name>.enc
- "$ENV_VAR" → resolved from process env (formerly silent)
- "<plain>" → used as-is (least secure, accepted for dev)
Public surface:
- GrobStore::set_secret / get_secret / list_secrets / remove_secret
- grob secrets add (reads value from stdin, supports `printf | grob ...`)
- grob secrets list (names only, --json supported)
- grob secrets show (redacted by default, --unsafe-show to reveal)
- grob secrets rm (interactive confirm, --force to skip)
Resolution happens once in init_core_services::resolve_provider_secrets,
which clones config.providers and substitutes resolved api_keys. The
ProviderConfig struct itself is unchanged (full backward compatibility).
Tests: 4 unit tests for the storage roundtrip + list-no-leak + remove +
overwrite; 2 unit tests for the CLI redaction helper. End-to-end
verified via the actual `grob secrets {add,list,show,rm}` flow.
Doc: NEW docs/how-to/manage-secrets.md with migration steps from env
vars and trade-offs vs Vault / KMS (coming via SecretBackend trait,
P-276).
Out of scope (follow-up):
- grob secrets export-key / import-key (PBKDF2 password wrap)
- SecretBackend trait + File / Vault / KMS backends (P-276)
6 tasks
This was referenced Apr 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an encrypted-at-rest store for upstream provider API keys, reusing the existing
StorageCipher(AES-256-GCM) used for OAuth tokens. The cleartext key never touchesconfig.toml, shell history, or dotfiles.Three modes for
[[providers]] api_key(resolved at startup)secret:<name>~/.grob/secrets/<name>.enc(AES-GCM)$ENV_VAR<plain string>A startup log line confirms each resolution:
Public surface
addreads the value from stdin and rejects empty input.listnever displays values (verified by unit test).showis redacted by default;--unsafe-showreveals.Files changed
src/storage/mod.rs—set_secret/get_secret/list_secrets/remove_secret+ 4 unit testssrc/commands/secrets.rs— new module (4 actions + 2 redaction unit tests)src/commands/mod.rs— module declarationsrc/cli/args.rs—Commands::Secrets+enum SecretsActionsrc/main.rs— dispatchsrc/server/init.rs—resolve_provider_secrets()clones the providers list and substitutes resolved keys (backward compat:ProviderConfigstruct unchanged)docs/how-to/manage-secrets.md— migration steps from env vars, trade-offs vs Vault/KMSTest plan
cargo test --lib storage::tests::test_secret— 4 passedcargo test --lib commands::secrets::— 2 passedprintf 'sk-x' | grob secrets add foo,grob secrets list(no value leak),grob secrets show foo(redacted),grob secrets show foo --unsafe-show(revealed),grob secrets rm foo --forcecargo clippy --lib --tests --bin grob -- -D warningscleanOut of scope (follow-up)
grob secrets export-key --to <file> --password <prompt>andimport-keyfor portable backup of the master AES key (PBKDF2 password wrap)SecretBackendtrait +File/Vault/KMSbackends — see P-276 (feat/secret-backend-trait, drafted)Migration
Drop-in compatible with existing
$VARproviders (now actually resolved). For new secrets: