Skip to content

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

Description

@AbirAbbas

Seen on dev@debffabf6.

What happened

Both belts pass the parent process environment to bash commands run for a model. When OPENROUTER_API_KEY is exported, the child receives it. JobShellEnv removes tmux variables but does not remove provider credentials. A key stored only in the profile is read by config.APIKeyAt; that function does not export it into codeaf's process environment, so the profile-only case does not by itself expose OPENROUTER_API_KEY to the child.

Replication

Deterministic (no model). Save as internal/exec/bare/shell_env_key_test.go and run go test ./internal/exec/bare -run TestAProviderKeyDoesNotReachAModelsShell -count=1:

package bare

import (
	"strings"
	"testing"
)

// A provider key in codeaf's environment must not reach a model's shell.
func TestAProviderKeyDoesNotReachAModelsShell(t *testing.T) {
	t.Setenv("OPENROUTER_API_KEY", "probe-not-a-key")
	for _, entry := range StreamingEnv() {
		if strings.HasPrefix(entry, "OPENROUTER_API_KEY=") {
			t.Fatal("OPENROUTER_API_KEY reaches the environment of the model's shell")
		}
	}
}

Today it fails with OPENROUTER_API_KEY reaches the environment of the model's shell. StreamingEnv is the seam the foreground bash tool and the job registry both use.

Field (real models). With OPENROUTER_API_KEY exported, in a throwaway repository: codeaf do --json 'run exactly this shell command and report its output: if [ -n "${OPENROUTER_API_KEY+x}" ]; then echo key-set; else echo key-unset; fi'. Under a minute, a fraction of a cent. Today the reply reports key-set. The command never prints the value.

Where

At dev@debffabf6, internal/exec/bare/streaming.go:120, StreamingEnv, starts from os.Environ(); internal/exec/tools.go:1594 and :1938, JobShellEnv, preserve provider keys. internal/config/apikey.go:60, APIKeyAt, reads a profile key without setting an environment variable.

The fix

Give model-run child processes a scrubbed environment that removes provider credentials by default. Provide a deliberate opt-in pass-through for a task that genuinely needs one.

Acceptance

  • e2e: a bash call on each belt prints key-unset for a provider key inherited by codeaf unless an explicit pass-through was granted.
  • Unit: environment filtering covers exported and profile-only keys without logging secret values.
  • Document the opt-in and record the former inheritance in invalidates.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:sessionThe engine — turns, tasks, the toolbelt, checkpointsarea:toolsThe tool layer: built-in tools, registries, beltsbugSomething the code does that it should notsev:seriousWrong or missing behaviour a person meets in ordinary use

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions