feat(secrets): inject bound vault secrets into agent shells as env vars - #276
Merged
Merged
Conversation
Let a user bind vault secrets to a task so their values are injected into the agent's shell as environment variables, without the plaintext ever entering the model's text channel, the renderer, or logs. The model can run `curl -H "Authorization: Bearer $KEY"` but cannot read $KEY's value. - Add optional POSIX `envVarName` to a secret (schema, vault, IPC, settings form) with a reserved-key denylist and global uniqueness. - Plumb `boundSecretIds` (ids only) through runtimeOptions, the prompt-draft runtime overrides, persistence, and buildProviderRuntimeOptions. - Add a main-process-only resolver (resolveBoundSecretEnv) that maps ids to an env map, skipping non-injectable / reserved / duplicate names. Never exposed via preload; logs counts and names only, never values. - Claude: spread resolved env into options.env for the primary turn only, so secrets stay out of diagnostics and every aux/introspection query. - Codex: inject via per-thread shell_environment_policy.set.* overrides and fold a name-only fingerprint into the thread key so a changed binding forces a fresh thread. Fix a latent bug where thread/resume dropped caller config overrides (MCP isolation + secrets) entirely. - Add a composer control to bind secrets, update Settings copy to reflect the honest trade-off, and document the invariant in AGENTS.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
| Commit | Scanned at | New | Resolved | Net |
|---|---|---|---|---|
ee62f25 |
2026-07-29 08:27 UTC | — | — | — |
ee62f25 < |
2026-07-29 08:27 UTC | — | — | — |
Last scanned: ee62f25 · 2026-07-29 08:27 UTC
paul-seo73
approved these changes
Jul 29, 2026
Merged
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
Lets a user bind a vault secret to a task so its value is injected into the agent's shell as an environment variable — the model can use it (
curl -H "Authorization: Bearer $KEY") but never see the plaintext. This is the "C" option from the design discussion: consumption without exposure.The value flows Electron-main → child-process env only. It never enters
runtimeOptions(ids only), never crosses preload for runtime use, and never lands in prompts, transcripts,BridgeEvents, diagnostics, or logs.What changed
Data model (
envVarNameon a secret)envVarNameon the stored secret (secret-vault.ts,secrets.ts, IPC schema, settings form). NoVAULT_VERSIONbump — older files load fine.PATH,CLAUDE_CONFIG_DIR,CODEX_HOME,STAVE_LOCAL_MCP_TOKEN,ELECTRON_*, …) + global uniqueness among env-var names.Binding (
boundSecretIds, ids only)RuntimeOptionsObjectSchema,ProviderRuntimeOptions,PromptDraftRuntimeOverrides(+ its schema),ResolvedPromptDraftRuntimeState, andbuildProviderRuntimeOptions.SecretBindingControl) to pick which injectable secrets bind to the task. Shows masked preview + env-var name only.Resolver (main-process only)
resolveBoundSecretEnv({ ids })maps ids → env map, skipping non-injectable / reserved / duplicate names. Not exposed via preload. Logs counts + names only, never values.electronis now lazy-required so importing it into the runtimes doesn't break the bun test import graph.Claude injection
secretEnvspread last intooptions.env, for the primary turn only — kept out ofbuildClaudeDiagnosticsand every aux/introspection query by construction.Codex injection + resume-drop fix
shell_environment_policy.set.<KEY>overrides (same channel as the NVM PATH shim), merged intoconfigOverrides.thread/start.thread/resumepreviously dropped callerconfigOverridesentirely (MCP isolation + our secrets). Now forwarded on both start and resume.Testing
bun run typecheckclean.bun run check:max-lines-ratchet/check:switch-exhaustiveness/check:reliability-gatespass.tests/secrets.test.ts(+13: env-var normalize, denylist, vault round-trip, legacy load, uniqueness,resolveEnvForIds),tests/codex-app-server-secret-env.test.ts(resume/start override forwarding),tests/prompt-draft-runtime.test.ts(+2:boundSecretIdsresolution + equality).mainbaseline (remaining failures are pre-existing environmental: nvm binary discovery, fs/timeout).Not done / follow-ups
printenv NAMEin the agent shell; confirm value present in shell, absent from transcript) was not run in this environment — the code path is unit-covered but not exercised against a live Electron session.🤖 Generated with Claude Code