Skip to content

feat: environment variable filtering with filter_env, allowed_env, disallowed_env#44

Closed
ppsplus-bradh wants to merge 10 commits intoguess:mainfrom
ppsplus-bradh:feat/env-var-filtering
Closed

feat: environment variable filtering with filter_env, allowed_env, disallowed_env#44
ppsplus-bradh wants to merge 10 commits intoguess:mainfrom
ppsplus-bradh:feat/env-var-filtering

Conversation

@ppsplus-bradh
Copy link
Copy Markdown
Contributor

@ppsplus-bradh ppsplus-bradh commented Mar 28, 2026

Summary

Adds a complete environment variable control surface for CLI session spawning. Three new session options give users fine-grained control over which system env vars reach the CLI:

Option Type Default Purpose
filter_env boolean true Toggle built-in allowlist filtering on/off
allowed_env [String.t()] [] Additional keys to pass through (additive to built-in allowlist)
disallowed_env [String.t()] [] Keys to exclude (works in both modes)

Naming follows the SDK's existing convention (allowed_tools/disallowed_tools).

Depends on

How it works

Filtered mode (filter_env: true, default):

System.get_env()
  → filter: key in (built-in allowlist OR allowed_env) AND key not in disallowed_env
  → Map.new()

Unfiltered mode (filter_env: false):

System.get_env()
  → reject: key in disallowed_env
  → Map.new()

Setting filter_env: false with no other options reproduces the pre-filter behavior exactly — all system env vars pass through unchanged.

Both paths then merge identically with SDK vars, user :env overrides, and API key.

Examples

# Default: secure minimum, add specific vars
ClaudeCode.start_link(allowed_env: ["DATABASE_URL"])

# Everything except secrets
ClaudeCode.start_link(filter_env: false, disallowed_env: ["RELEASE_COOKIE", "GITHUB_SSH_KEY"])

# Pre-filter behavior (no filtering)
ClaudeCode.start_link(filter_env: false)

Changes

  • port.ex: Built-in allowlists (@cli_env_prefixes, @cli_env_allowlist, @system_env_allowlist), collect_system_env/3, filter_system_env/1, cli_env_var?/2
  • options.ex: Schema definitions for filter_env, allowed_env, disallowed_env
  • command.ex: convert_option nil returns for new options
  • port_test.exs: Tests for filtering, allowlist, prefix matching, exclusion
  • CHANGELOG.md: Documented under Added

@guess
Copy link
Copy Markdown
Owner

guess commented Mar 28, 2026

also this - i did a squash + merge so just have to remove the #42 commits

ppsplus-bradh and others added 10 commits March 28, 2026 18:16
Prevents leaking sensitive host environment (SSH keys, database URLs,
cloud credentials) to the CLI subprocess. Filters by CLI-recognized
prefixes (ANTHROPIC_, CLAUDE_CODE_, CLAUDE_, VERTEX_REGION_), an
explicit allowlist of non-namespaced CLI vars, and essential system
vars (PATH, HOME, etc.). User-provided :env bypasses the filter.
Add `allowed_env` option that accepts a list of environment variable
names to pass through from the system environment to the CLI, beyond
the built-in allowlist. Unlike `env` (key-value pairs), `allowed_env`
takes only keys — values are read from System.get_env() at spawn time.

This enables applications to forward specific env vars (e.g.
DATABASE_URL, custom config) without hardcoding values in the `env`
map, while still benefiting from the security filtering that excludes
RELEASE_*, SSH keys, and other sensitive process-level vars.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete the env control surface with two new options:

- `filter_env` (boolean, default true) — when true, applies the
  built-in allowlist (ANTHROPIC_*, CLAUDE_*, PATH, HOME, etc.).
  When false, passes all system env vars through unfiltered.

- `disallowed_env` (list of strings) — keys to exclude from the
  CLI environment. Works in both filtered and unfiltered modes.

Combined with the existing `allowed_env` and `env` options, this
gives users full control over what reaches the CLI:

  # Filtered (default): built-in allowlist + extras
  filter_env: true, allowed_env: ["DATABASE_URL"]

  # Unfiltered: everything minus exclusions
  filter_env: false, disallowed_env: ["RELEASE_COOKIE", "SECRET_KEY"]

  # Explicit overrides always win regardless of mode
  env: %{"FORCE_THIS" => "value"}

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ppsplus-bradh ppsplus-bradh force-pushed the feat/env-var-filtering branch from 8b7b514 to 63a7c0d Compare March 28, 2026 23:18
@ppsplus-bradh
Copy link
Copy Markdown
Contributor Author

Rebased on main.

@col
Copy link
Copy Markdown
Contributor

col commented Mar 29, 2026

Very keen to see this merged. Nice work @ppsplus-bradh !

@guess
Copy link
Copy Markdown
Owner

guess commented Mar 29, 2026

So disallowed_env is already implicitly supported through :env since erlang's port already supports false as a value to unset env variables: https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2

So I think I'd rather maintain consistency with how the Python SDK behaves and pass all system env by default. You can use :env to override or unset them. And we can add a single allowlist option if you want to explicitly define the allowed env that can be inherited from the system. Then instead of 4 env-related options, we'll just have 2.

I'm working on a PR for this now.

@ppsplus-bradh
Copy link
Copy Markdown
Contributor Author

So disallowed_env is already implicitly supported through :env since erlang's port already supports false as a value to unset env variables: https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2

So I think I'd rather maintain consistency with how the Python SDK behaves and pass all system env by default. You can use :env to override or unset them. And we can add a single allowlist option if you want to explicitly define the allowed env that can be inherited from the system. Then instead of 4 env-related options, we'll just have 2.

I'm working on a PR for this now.

Awesome! Didn't realize we already inherently had that level of flexibility.

@guess
Copy link
Copy Markdown
Owner

guess commented Mar 30, 2026

@ppsplus-bradh thanks again for your help/work on these 🙏

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants