Skip to content

Strip provider API keys from a model's shell environment - #1486

Merged
AbirAbbas merged 4 commits into
Agent-Field:devfrom
biggdawg320:jobscout/1484-provider-keys-in-the-process-environment
Sep 30, 2026
Merged

AbirAbbas merged 4 commits into
Agent-Field:devfrom
biggdawg320:jobscout/1484-provider-keys-in-the-process-environment

Conversation

@biggdawg320

Copy link
Copy Markdown

Both the foreground bash tool and the background job registry built a model's shell environment through JobShellEnv, which stripped the tmux floor but otherwise passed codeaf's whole process environment through — so an exported OPENROUTER_API_KEY, OPENAI_API_KEY, another vendored provider's key, or a person's own custom-named service key was directly readable by any shell command the model ran. JobShellEnv now removes every provider credential env var codeaf knows about, including custom key_env names from the profile's persisted sources, reading the new CODEAF_ALLOW_PROVIDER_KEYS_IN_SHELL opt-in through internal/env's owned-variable door (env.Get) rather than os.Getenv, so it obeys the repository's environment law and carries the standard AFORGE_ legacy fallback. The opt-in is registered on settings.go's OperatorEnvPins allowlist, documented in the running-from-the-terminal manual page, and recorded in a new changelog entry (docs/changes/unreleased/1484-provider-keys-in-shell.md) noting that shell commands used to inherit these keys and no longer do. Added deterministic unit tests covering the strip, the opt-in, and custom persisted-source key variables (internal/exec/bare/shell_env_key_test.go, internal/exec/providerkeyenv_test.go).

Fixes #1484

Testing

  • Added a test (internal/exec/bare/shell_env_key_test.go) that fails on the current code and passes with this change.
  • The surrounding tests pass: GOTOOLCHAIN=auto go test -timeout 5m -count=1 ./internal/exec/bare/... ./internal/config/... && GOTOOLCHAIN=auto go test -timeout 3m -count=1 -run 'TestJobShellEnvCannotReachTheHostTmux|TestBackgroundJobShellCannotReachTheHostTmux' ./internal/exec/...

This change was drafted with AI assistance (Claude) and verified against the project's own test suite before submission.

JobShellEnv passed the parent process environment straight through to
every bash command a model runs, TMUX floor aside, so an exported
OPENROUTER_API_KEY (or OPENAI_API_KEY, a vendored service's own
KeyEnv, or a person's own custom-named service key) was readable and
printable from inside the sandboxed shell on both belts: the
foreground bash tool and the background job registry.

JobShellEnv now also strips every provider credential env var codeaf
knows about — config.APIKeyEnv, OPENAI_API_KEY, each
modelsource.Vendored() source's KeyEnv, and each connected service's
custom key_env from the profile's persisted sources — unless
CODEAF_ALLOW_PROVIDER_KEYS_IN_SHELL is set in codeaf's own
environment, read through env.Get so it carries the same AFORGE_
fallback every owned variable does. A profile-only key was never
exported into os.Environ() in the first place, so it needed no
change.

Registers the new opt-in on settings.go's OperatorEnvPins allowlist,
documents it in running-from-the-terminal.md, and adds a changelog
entry recording the invalidated belief that shell commands used to
inherit these keys.

Co-Authored-By: Claude <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Sep 24, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@santoshkumarradha

Copy link
Copy Markdown
Member

Hey, thanks for the PR. Could you sign the CLA when you get a chance so we can review it?

@santoshkumarradha santoshkumarradha added bug Something the code does that it should not area:tools The tool layer: built-in tools, registries, belts labels Sep 27, 2026
@santoshkumarradha santoshkumarradha added this to the Reliable agent milestone Sep 27, 2026
@santoshkumarradha santoshkumarradha added the sev:critical Data loss, money spent wrongly, a false done, or the merge queue blocked label Sep 27, 2026
AbirAbbas and others added 3 commits September 30, 2026 11:51
…e tests keep to a temp profile

A connected service can name an owned CODEAF_ variable for its key, and every owned variable is still read under its former prefix, so that spelling reached a model's shell. It is now stripped with the rest. The new tests re-root the profile in a temporary directory instead of reading the operator's own, check that non-credential variables such as base URLs and proxies survive, and check that a child command cannot grant its own opt-in. The comment that spelled the retired prefix is respelled so the naming law passes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The page said no key reaches a model's shell command. The variables are removed, but a shell with file access can still read a key saved in the profile, and the opt-in only keeps a key that was already exported. The page now says both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It carried the issue number, 1484, so the pull-request gate would not find the entry for this change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AbirAbbas

Copy link
Copy Markdown
Collaborator

Thanks for this, it's a real fix. I pushed three small commits on top so it can go in:

  • the new tests now use a temporary profile instead of reading whoever runs them's real one, and a custom CODEAF_ key variable's former spelling is stripped too
  • the manual says exactly what the strip covers: a shell with file access can still read a key saved in the profile, and the opt-in only keeps a key that was already exported
  • the change entry is named for this PR (Strip provider API keys from a model's shell environment #1486) rather than the issue, and a comment that spelled the retired prefix is respelled so the naming check passes

Checked the behaviour with the real binary against a local model stub: provider keys print as unset in both foreground and background commands, base URL and proxy variables survive, and the opt-in brings the keys back.

@AbirAbbas
AbirAbbas merged commit b8dbfc5 into Agent-Field:dev Sep 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:tools The tool layer: built-in tools, registries, belts bug Something the code does that it should not sev:critical Data loss, money spent wrongly, a false done, or the merge queue blocked

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provider keys in the process environment reach model-run bash commands

4 participants