You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#2006 added <NAME>_FILE, which keeps secrets out of env listings, /proc/<pid>/environ, shell history, child processes and settings.json. That stops accidental leaks. It does not stop a process running as the same user from deliberately reading a secret. The secret is still plaintext on disk, and its path is in the environment or settings.json, so the file is easy to find.
That gap matters here in particular. This workflow runs many concurrent agent sessions (Claude Code, Codex, pi) as the user, and the claude-cli backend's --claude-cli-allow-tools escape hatch runs a tool-capable nested session driven by untrusted prompt content (diffs, commit messages, Jira text). A prompt-injection payload in any of these can cat a secret file as easily as it can read an environment variable.
Proposal
Let a secret be fetched on demand from a store that doesn't keep it as plaintext on disk. It would be one more source in utils::secret_env, next to <NAME> and <NAME>_FILE. The call sites would not change, because they already go through secret_var/secret_var_any (STYLE-0030).
Option A: <NAME>_COMMAND (credential helper)
<NAME>_COMMAND names a command whose stdout is the secret. This follows git credential helpers and AWS credential_process. Examples:
Whether a _COMMAND value in settings.json is acceptable. Anything that can write settings.json could then run arbitrary commands. Perhaps restrict _COMMAND to the process environment only, or warn.
The claude-cli scrub must also remove *_COMMAND for secret names.
Option B: native OS keychain
Read secrets directly from the macOS Keychain or the Linux Secret Service (for example with the keyring crate), under a fixed service name such as omni-dev and an account named after the variable. The auth login commands could write there instead of to settings.json.
It is simpler for users, and nothing about it lives in the environment.
It adds a dependency and platform-specific behaviour. On macOS the ACL is per binary, and omni-dev installs unsigned, so every upgrade may trigger a new keychain prompt. The Accessibility grant (ADR-0058) has the same upgrade problem.
A headless daemon has no keychain on Linux without a Secret Service session.
A is the more general design, and B can be built on top of A (security find-generic-password …). Recommend deciding between them in an ADR that extends ADR-0089.
Acceptance
An ADR choosing A, B or both, and settling the open questions above.
The resolver supports the chosen sources, with MapEnv tests and no mutation of the process environment. For A, use a fake command.
The grep guards and the registry are unchanged. New sources apply to every registered secret automatically.
The claude-cli scrub covers the new variable names.
Problem
#2006 added
<NAME>_FILE, which keeps secrets out ofenvlistings,/proc/<pid>/environ, shell history, child processes andsettings.json. That stops accidental leaks. It does not stop a process running as the same user from deliberately reading a secret. The secret is still plaintext on disk, and its path is in the environment orsettings.json, so the file is easy to find.That gap matters here in particular. This workflow runs many concurrent agent sessions (Claude Code, Codex, pi) as the user, and the
claude-clibackend's--claude-cli-allow-toolsescape hatch runs a tool-capable nested session driven by untrusted prompt content (diffs, commit messages, Jira text). A prompt-injection payload in any of these cancata secret file as easily as it can read an environment variable.Proposal
Let a secret be fetched on demand from a store that doesn't keep it as plaintext on disk. It would be one more source in
utils::secret_env, next to<NAME>and<NAME>_FILE. The call sites would not change, because they already go throughsecret_var/secret_var_any(STYLE-0030).Option A:
<NAME>_COMMAND(credential helper)<NAME>_COMMANDnames a command whose stdout is the secret. This follows git credential helpers and AWScredential_process. Examples:security,pass, Vault,secret-tool, and so on.shell-wordsversussh -c. The ADR must decide. Shell interpretation is a larger surface area.NAMEandNAME_FILEwithin a layer. This should extend feat: support <NAME>_FILE for every secret environment variable #2006's per-layer pair to a triple._COMMANDvalue insettings.jsonis acceptable. Anything that can writesettings.jsoncould then run arbitrary commands. Perhaps restrict_COMMANDto the process environment only, or warn.claude-cliscrub must also remove*_COMMANDfor secret names.Option B: native OS keychain
Read secrets directly from the macOS Keychain or the Linux Secret Service (for example with the
keyringcrate), under a fixed service name such asomni-devand an account named after the variable. Theauth logincommands could write there instead of tosettings.json.A is the more general design, and B can be built on top of A (
security find-generic-password …). Recommend deciding between them in an ADR that extends ADR-0089.Acceptance
MapEnvtests and no mutation of the process environment. For A, use a fake command.claude-cliscrub covers the new variable names.Follow-up to #2006 / #2007.