Skip to content

CLI child processes inherit the router's whole environment, and the scrub list can be changed over the admin API #179

Closed

Description

@siebe41

What

The CLI connectors start from the router's whole environment and remove only a short list of names for each preset:

  • Claude Code: ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL (CliAdapters.cs:150-151)
  • Codex: CODEX_API_KEY (CliAdapters.cs:324)
  • Copilot and Custom: nothing

Everything else reaches the child process:

  • other providers' keys (OPENAI_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY)
  • dashboard tokens and gateway keys (ROUTER_ADMIN_TOKEN, ROUTER_GATEWAY_KEY in the container, SemanticRouter__Middleware__GatewayKeys__*)
  • cloud credentials

SECURITY.md promises "a scrubbed environment" and lists "an API key or other variable leaking into the child" as in scope.

There is a second gap. RemoveEnvironmentVariables and PromptInput are not treated as process settings. RouterConfiguration.SameProcess and ProcessSettingChanges (src/SemanticRouter.AspNetCore/Runtime/RouterConfiguration.cs:700-737) compare only type, preset, executable, home directory, arguments and EnvironmentVariables. So an admin-API caller can change either setting without AllowProcessSettings:

  • Setting RemoveEnvironmentVariables: [] on the Claude Code preset brings back ANTHROPIC_API_KEY (billing switches to the API key) and ANTHROPIC_BASE_URL.
  • Setting PromptInput: Argument puts prompts on the command line, where other users can read them.

Evidence

  • src/SemanticRouter.AspNetCore/Connectors/Cli/CliProcess.cs:101-109: the remove list and then the set list are applied on top of the inherited environment.
  • src/SemanticRouter.AspNetCore/Connectors/Cli/CliChatConnector.cs:61: a configured list replaces the preset's defaults entirely.
  • docs/providers.md:199 documents only the three removed names.

Impact

The presets turn tools off, so a leak needs a CLI bug or a misconfigured Custom command. Still, any secret in the child's environment is one step from the model (a CLI's own diagnostics, a crash report, a Custom script). This is defence in depth.

Suggested fix

  • By default, remove every value the router itself knows to be secret: all resolved connector, embedding, gateway and dashboard secrets (by name when they came from ${env:NAME}), plus ROUTER_* and SemanticRouter__*.
  • Or start the child from an allow-list instead: PATH, HOME / USERPROFILE, LANG, TERM, TMPDIR and the CLI's documented configuration variables, plus EnvironmentVariables.
  • Make RemoveEnvironmentVariables add to the preset's defaults instead of replacing them.
  • Treat RemoveEnvironmentVariables and PromptInput as process settings in SameProcess.

Done when

  • By default, a CLI child does not see the router's other providers' keys, dashboard tokens or gateway keys. An integration test with the FakeCli checks this, as CliProcessIntegrationTests.cs:68-78 does for one variable.
  • Changing RemoveEnvironmentVariables or PromptInput over the admin API is refused without AllowProcessSettings, with a test.
  • docs/providers.md describes the child's environment.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions