Skip to content

fix(cli): prevent shell environment variable leakage into agent secrets#6360

Merged
0xbbjoker merged 6 commits intodevelopfrom
fix/env-variable-leakage-prevention
Jan 15, 2026
Merged

fix(cli): prevent shell environment variable leakage into agent secrets#6360
0xbbjoker merged 6 commits intodevelopfrom
fix/env-variable-leakage-prevention

Conversation

@0xbbjoker
Copy link
Collaborator

@0xbbjoker 0xbbjoker commented Jan 13, 2026

Summary

Fixes shell environment variable leakage into ElizaOS plugin loading decisions and agent secrets.

Problem: dotenv.config() does NOT override existing process.env values by default. This means shell environment variables (from .zshrc/.bashrc) leak into plugin loading:

User shell has: ANTHROPIC_API_KEY, OPENAI_API_KEY
Project .env has: OPENROUTER_API_KEY

Expected plugins: openrouter, bootstrap
Actual plugins: anthropic, openai, openrouter, bootstrap (LEAKED!)

Solution:

  • Scan installed plugins for declared env vars via package.json agentConfig.pluginParameters
  • Set global filter so only declared vars are captured into agent secrets
  • Load .env files with proper precedence (closest file wins)

Changes

  • packages/cli/src/utils/plugin-env-filter.ts - New utility to scan plugins for env var declarations
  • packages/core/src/secrets.ts - Add setAllowedEnvVars() filter to restrict captured vars
  • packages/core/src/utils/environment.ts - Add loadEnvFilesWithPrecedence() for proper hierarchy
  • packages/cli/src/commands/start/index.ts - Integrate filter before character loading

Test plan

  • Unit tests for plugin env var scanning (plugin-env-filter.test.ts)
  • Unit tests for secrets filtering (secrets-filtering.test.ts)
  • Unit tests for env precedence loading (env-precedence.test.ts)
  • Manual test with shell env vars vs project .env
  • Test in monorepo context

Notes

  • 40 tests passing
  • Backwards compatible (null filter = existing behavior)
  • Core env vars (POSTGRES_URL, LOG_LEVEL, etc.) always allowed

🤖 Generated with Claude Code


Note

Introduces plugin-based env var allowlisting and proper .env precedence to stop shell vars from leaking into agent config/secrets.

  • Adds utils/plugin-env-filter.ts to scan plugin package.json (agentConfig.pluginParameters) and build allowed var set; includes core defaults and warnings for undeclared official plugins
  • Core secrets.ts: new setAllowedEnvVars/getAllowedEnvVars; setDefaultSecretsFromEnv merges only allowed vars (character values take precedence); hasCharacterSecrets optimized
  • Core utils/environment.ts: adds findAllEnvFiles and loadEnvFilesWithPrecedence (closest wins, optional boundary/override); exposes resetEnvironmentCache; minor perf tidy-ups
  • CLI start command: runs plugin scan, sets allowed vars filter, warns on missing declarations, and loads .env with precedence before loading characters/agents
  • Adds focused unit tests for plugin env scanning, secrets filtering, env precedence, and environment utils

Written by Cursor Bugbot for commit f30034e. This will update automatically on new commits. Configure here.

Greptile Overview

Greptile Summary

This PR implements a robust solution to prevent shell environment variable leakage into ElizaOS agent secrets by introducing plugin-based environment variable filtering.

Key Changes

  • Plugin Environment Scanning: New plugin-env-filter.ts utility scans installed plugins' package.json files for declared environment variables via agentConfig.pluginParameters
  • Global Filter in Secrets Module: Added setAllowedEnvVars() to restrict which environment variables are captured into agent secrets
  • Environment File Precedence: New loadEnvFilesWithPrecedence() ensures closest .env file wins, respecting monorepo boundaries
  • Integration in Start Command: Filter is applied before character loading, preventing shell vars from leaking into plugin selection

Architecture

The solution follows a clear flow: scan plugins → set filter → load env files → filter on capture. Core environment variables (e.g., POSTGRES_URL, LOG_LEVEL) are always allowed. The filter is null by default for backward compatibility.

Issues Found

  • Line 72 in plugin-env-filter.ts already correctly checks for null declarations before accessing length
  • Line 19 in secrets.ts uses underscore variable as TypeScript idiom for unused loop variable (valid pattern)
  • Consider caching regex compilation in detectShellOnlyVars for performance
  • CORE_ALLOWED_VARS hardcoded in CLI could be centralized in core package

Testing

40 tests passing across 3 new test files with comprehensive coverage of filtering, precedence, and integration scenarios.

Confidence Score: 4/5

  • This PR is safe to merge with minor considerations
  • The implementation is well-tested with 40 passing tests, addresses a real security concern (shell env leakage), and maintains backward compatibility. Code quality is high with proper error handling and optimization. Minor issues include hardcoded core vars list and potential for regex caching, but these don't affect functionality.
  • Pay close attention to packages/cli/src/utils/plugin-env-filter.ts for the regex caching optimization and consider centralizing CORE_ALLOWED_VARS

Important Files Changed

File Analysis

Filename Score Overview
packages/cli/src/utils/plugin-env-filter.ts 4/5 added plugin environment variable scanning utility with deduplication and filtering logic
packages/core/src/secrets.ts 4/5 refactored to add environment variable filtering capability with global allowed vars set
packages/core/src/utils/environment.ts 4/5 added .env file precedence loading with boundary directory support
packages/cli/src/commands/start/index.ts 4/5 integrated plugin env scanning and filtering before character loading to prevent shell leakage

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Start Command
    participant Scanner as Plugin Scanner
    participant Core as Core (secrets.ts)
    participant Env as Environment Loader
    participant Agent as Agent/Character

    CLI->>Scanner: scanPluginsForEnvDeclarations(cwd)
    Scanner->>Scanner: Scan node_modules for plugins
    Scanner->>Scanner: Read package.json files
    Scanner->>Scanner: Extract pluginParameters
    Scanner-->>CLI: allowedVars Set + plugin info
    
    CLI->>Core: setAllowedEnvVars(allowedVars)
    Note over Core: Global filter set
    
    CLI->>Env: loadEnvFilesWithPrecedence(cwd)
    Env->>Env: findAllEnvFiles (parent dirs)
    Env->>Env: Load files (farthest to closest)
    Env-->>CLI: Loaded env files
    Note over CLI: process.env populated
    
    CLI->>Agent: Load character/agent
    Agent->>Core: setDefaultSecretsFromEnv(character)
    Core->>Core: Filter process.env by allowedVars
    Core->>Agent: Merge filtered vars into secrets
    Note over Agent: Only declared vars captured
Loading

Shell environment variables (from .zshrc/.bashrc) were leaking into
ElizaOS plugin loading decisions and agent secrets because dotenv
does NOT override existing process.env values by default.

This fix introduces:
- Plugin env var scanning: scans installed plugins for declared env vars
- Global filter: only declared env vars are captured into agent secrets
- Precedence loading: closest .env file values win over parent directories

Changes:
- Add scanPluginsForEnvDeclarations() to scan plugin package.json for
  agentConfig.pluginParameters declarations
- Add setAllowedEnvVars() filter in secrets.ts to restrict captured vars
- Add loadEnvFilesWithPrecedence() for proper .env hierarchy loading
- Update start command to set filter before any character loading

This prevents scenarios where a user's shell has ANTHROPIC_API_KEY but
their project .env only has OPENROUTER_API_KEY - previously both plugins
would load unexpectedly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 13, 2026

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/env-variable-leakage-prevention

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@0xbbjoker 0xbbjoker marked this pull request as ready for review January 13, 2026 16:12
@claude
Copy link
Contributor

claude bot commented Jan 13, 2026

Claude finished @0xbbjoker's task in 1m 49s —— View job


PR Review: Environment Variable Leakage Prevention

  • Review plugin-env-filter.ts implementation
  • Review secrets.ts changes
  • Review environment.ts changes
  • Review start/index.ts integration
  • Verify test coverage
  • Check for security issues
  • Final assessment

Overall Assessment: ✅ APPROVE

This PR properly addresses a security concern where shell environment variables could leak into plugin loading decisions and agent secrets. The implementation is well-designed with comprehensive test coverage.

What's Good

Security Fix: Properly prevents shell-based API keys from leaking into agent secrets
Test Coverage: 40 tests covering all new functionality (plugin scanning, secrets filtering, env precedence)
TypeScript: Proper typing throughout - no any types
Pattern Compliance: Uses @elizaos/core imports, bun:test for testing
Backwards Compatible: Null filter maintains existing behavior

Minor Suggestions

💡 SUGGESTION: require() usage in environment.ts

Lines 209-210, 235, 250-251 in packages/core/src/utils/environment.ts use require() for dynamic imports:

const fs = require('node:fs');
const nodePath = require('node:path');
const dotenv = require('dotenv');

While this works fine and is intentional for tree-shaking in browser builds, consider documenting why these are runtime requires vs ES imports in a comment to prevent future contributors from "fixing" them.

💡 SUGGESTION: Consider edge case in varsToClear

loadEnvFilesWithPrecedence() has a varsToClear option that deletes env vars before loading. This is potentially destructive if misused. Currently it's not exposed in the start command integration, which is good. Consider adding a note about why this exists and when it should/shouldn't be used.

Code Quality

Aspect Status
No hardcoded secrets
SQL injection N/A
XSS N/A
Tests present ✅ 40 tests
Uses bun:test
TypeScript types ✅ No any
@elizaos/core imports
Functional style
Error handling

Test Coverage

  • plugin-env-filter.test.ts - 16 tests covering plugin scanning
  • secrets-filtering.test.ts - 10 tests covering env var filtering
  • env-precedence.test.ts - 14 tests covering .env file precedence

The tests properly cover the integration flow preventing shell leakage scenario.


Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Move ENV_VAR_NAME_REGEX to module level to avoid recompiling on each
call to detectShellOnlyVars().

Addresses: #6360 (comment)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Jan 13, 2026

Claude finished @0xbbjoker's task in 50s —— View job


PR Review: Environment Variable Leakage Prevention

  • Review changed files and verify implementation
  • Check for security issues
  • Verify test coverage
  • Check for TypeScript issues
  • Verify bun-only commands
  • Final assessment

Overall Assessment: ✅ APPROVE

This PR properly addresses a security concern where shell environment variables could leak into plugin loading decisions and agent secrets. The implementation is well-designed with comprehensive test coverage.

What's Good

Aspect Status
Security fix ✅ Properly prevents shell API key leakage
Test coverage ✅ 40 tests across 3 test files
TypeScript types ✅ No any types, proper interfaces
Testing framework ✅ Uses bun:test exclusively
Import patterns ✅ Uses @elizaos/core correctly
Backwards compatible ✅ Null filter = existing behavior
Error handling ✅ Proper try/catch with early returns

Previous Review Comments Addressed

The regex caching suggestion from Greptile was addressed in commit dca4d4e - the ENV_VAR_NAME_REGEX is now cached at module level (line 33 of plugin-env-filter.ts).

Minor Observations (Non-blocking)

💡 SUGGESTION: require() usage in environment.ts

Lines 209-210, 235, 250-251 use dynamic require() for fs, path, and dotenv. This is intentional for tree-shaking in browser builds, but consider adding a brief comment explaining why these are runtime requires vs ES imports to prevent future contributors from "fixing" them.

💡 SUGGESTION: CORE_ALLOWED_VARS location

CORE_ALLOWED_VARS is defined in CLI (plugin-env-filter.ts:19-30) but conceptually belongs with core env handling. Consider centralizing this in @elizaos/core in a future refactor for easier maintenance.

💡 SUGGESTION: varsToClear documentation

loadEnvFilesWithPrecedence() has a varsToClear option that deletes env vars before loading. This is potentially destructive if misused. Currently it's not exposed in the start command integration (good), but consider adding a JSDoc note about when this should/shouldn't be used.

Test Coverage Summary

Test File Tests Coverage
plugin-env-filter.test.ts 16 Plugin scanning, filtering, shell detection
secrets-filtering.test.ts 10 Env var filtering, character precedence
env-precedence.test.ts 14 .env file discovery and loading order

All tests properly verify the integration flow preventing shell leakage.

Security Check ✅

  • No hardcoded secrets
  • No SQL injection vectors (N/A)
  • No XSS vectors (N/A)
  • Proper input validation on env vars
  • Filter prevents arbitrary env var capture

Verdict: Ship it! The implementation is solid, well-tested, and addresses a real security concern.

The test for detecting browser environment was failing because
detectEnvironment() now caches its result at module level. Added
resetEnvironmentCache() call before mocking browser environment
to ensure the test gets a fresh detection.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Jan 13, 2026

Claude finished @0xbbjoker's task in 1m 1s —— View job


PR Review: Environment Variable Leakage Prevention

  • Analyze PR changes and context
  • Review plugin-env-filter.ts implementation
  • Review secrets.ts changes
  • Review environment.ts changes
  • Review start/index.ts integration
  • Verify test coverage
  • Check for security issues
  • Final assessment

Overall Assessment: ✅ APPROVE

This PR properly addresses a security concern where shell environment variables could leak into plugin loading decisions and agent secrets. The implementation is well-designed with comprehensive test coverage (40 tests across 3 test files).


What's Good

Aspect Status
Security fix ✅ Properly prevents shell API key leakage
Test coverage ✅ 40 tests - plugin scanning, secrets filtering, env precedence
TypeScript types ✅ No any types, proper interfaces
Testing framework ✅ Uses bun:test exclusively
Import patterns ✅ Uses @elizaos/core correctly
Backwards compatible ✅ Null filter = existing behavior
Error handling ✅ Proper try/catch with early returns
Regex optimization ✅ Cached at module level (line 33 of plugin-env-filter.ts)

Code Quality Review

packages/cli/src/utils/plugin-env-filter.ts - Well structured with:

  • Module-level regex caching (addressed Greptile suggestion)
  • Single-pass package.json parsing for efficiency
  • Proper null check on line 75 (addressed Greptile false positive)
  • Clean separation of concerns with helper functions

packages/core/src/secrets.ts - Clean implementation:

  • The for (const _ in secrets) pattern on line 19 is an intentional optimization to check object emptiness without array allocation
  • Efficient iteration over allowed vars when filter is smaller than process.env

packages/core/src/utils/environment.ts - Solid design:

  • Proper precedence loading (closest .env wins)
  • Boundary directory support for monorepo containment
  • Backwards-compatible override option

packages/cli/src/commands/start/index.ts - Good integration:

  • Filter set before character loading
  • Proper error handling with fallback

Minor Observations (Non-blocking)

💡 SUGGESTION: Document require() usage in environment.ts

Lines 209-210, 235, 250-251 use dynamic require() for fs, path, and dotenv. This is intentional for tree-shaking in browser builds, but a brief comment explaining this would prevent future contributors from "fixing" them.

💡 SUGGESTION: CORE_ALLOWED_VARS centralization

CORE_ALLOWED_VARS is defined in CLI (plugin-env-filter.ts:19-30) but conceptually belongs with core env handling. Consider centralizing in @elizaos/core in a future refactor.


Test Coverage Summary

Test File Tests Coverage
plugin-env-filter.test.ts 16 Plugin scanning, filtering, shell detection
secrets-filtering.test.ts 10 Env var filtering, character precedence
env-precedence.test.ts 14 .env file discovery and loading order

All tests properly verify the integration flow preventing shell leakage.


Security Check ✅

  • No hardcoded secrets
  • No SQL injection vectors (N/A)
  • No XSS vectors (N/A)
  • Proper input validation on env vars
  • Filter prevents arbitrary env var capture

Verdict: Ship it! The implementation is solid, well-tested, and addresses a real security concern.

@claude
Copy link
Contributor

claude bot commented Jan 15, 2026

Claude finished @0xbbjoker's task in 1m 35s —— View job


PR Review: Environment Variable Leakage Prevention

  • Analyze PR changes and verify implementation
  • Check for security issues
  • Verify test coverage
  • Check for TypeScript issues
  • Verify bun-only commands
  • Final assessment

Overall Assessment: ✅ APPROVE

This PR properly addresses a security concern where shell environment variables could leak into plugin loading decisions and agent secrets. The implementation is well-designed with comprehensive test coverage.


What's Good

Aspect Status
Security fix ✅ Properly prevents shell API key leakage
Test coverage ✅ 40 tests across 3 test files
TypeScript types ✅ No any types, proper interfaces
Testing framework ✅ Uses bun:test exclusively
Import patterns ✅ Uses @elizaos/core correctly
Backwards compatible ✅ Null filter = existing behavior
Error handling ✅ Proper try/catch with early returns
Regex optimization ✅ Cached at module level (line 33 of plugin-env-filter.ts)

Previous Review Comments Status

All prior review comments have been addressed:

  • ✅ Regex caching suggestion (commit dca4d4e) - ENV_VAR_NAME_REGEX now cached at module level
  • ✅ Null check on line 75 (false positive) - code already includes declarations && guard
  • ✅ Underscore variable pattern on line 19 - intentional optimization for object emptiness check

Minor Observations (Non-blocking)

💡 SUGGESTION: Document require() usage in environment.ts

Lines 209-210, 235, 250-251 use dynamic require() for fs, path, and dotenv. This is intentional for tree-shaking in browser builds, but a brief comment explaining this would prevent future contributors from "fixing" them.

💡 SUGGESTION: CORE_ALLOWED_VARS centralization

CORE_ALLOWED_VARS is defined in CLI (plugin-env-filter.ts:19-30) but conceptually belongs with core env handling. Consider centralizing in @elizaos/core in a future refactor.


Test Coverage Summary

Test File Tests Coverage
plugin-env-filter.test.ts 16 Plugin scanning, filtering, shell detection
secrets-filtering.test.ts 10 Env var filtering, character precedence
env-precedence.test.ts 14 .env file discovery and loading order

All tests properly verify the integration flow preventing shell leakage.


Security Check ✅

  • No hardcoded secrets
  • No SQL injection vectors (N/A)
  • No XSS vectors (N/A)
  • Proper input validation on env vars
  • Filter prevents arbitrary env var capture

Verdict: Ship it! The implementation is solid, well-tested, and addresses a real security concern.

@claude
Copy link
Contributor

claude bot commented Jan 15, 2026

Claude finished @0xbbjoker's task in 1m 29s —— View job


PR Review: Environment Variable Leakage Prevention

  • Analyze PR changes and context
  • Review changed files for issues
  • Check test coverage
  • Verify security considerations
  • Final assessment

APPROVE

This PR properly addresses a security concern where shell environment variables could leak into plugin loading decisions and agent secrets. The implementation is well-designed with comprehensive test coverage.


Summary

Aspect Status
Security fix ✅ Properly prevents shell API key leakage
Test coverage ✅ 40+ tests across 3 test files (857 lines)
TypeScript types ✅ No any types, proper interfaces
Testing framework ✅ Uses bun:test exclusively
Import patterns ✅ Uses @elizaos/core correctly
Backwards compatible ✅ Null filter = existing behavior
Error handling ✅ Proper try/catch with early returns

Previous Review Comments - All Addressed

  • Regex caching (commit dca4d4e) - ENV_VAR_NAME_REGEX cached at module level (line 33)
  • Null check on line 75 - already has declarations && guard (false positive)
  • Underscore variable on line 19 - intentional optimization pattern for object emptiness check

Code Quality Highlights

plugin-env-filter.ts - Well structured:

  • Single-pass package.json parsing via parsePackageJson()
  • Module-level regex caching
  • Clean helper function separation

secrets.ts - Efficient implementation:

  • Iterates over smaller set (allowedVars) when filter is set
  • Maintains backward compatibility with null filter

environment.ts - Solid design:

  • Proper precedence loading (closest .env wins)
  • Boundary directory support for monorepo containment

start/index.ts - Clean integration:

  • Filter set before character loading
  • Proper error handling with fallback

Minor Observations (Non-blocking)

💡 SUGGESTION: Document require() usage

Lines 209-210, 235, 250-251 in environment.ts use dynamic require() for fs, path, and dotenv. This is intentional for tree-shaking in browser builds. Consider adding a brief comment to prevent future contributors from "fixing" them.

💡 SUGGESTION: Centralize CORE_ALLOWED_VARS

CORE_ALLOWED_VARS is defined in CLI but conceptually belongs in @elizaos/core. Consider centralizing in a future refactor.


Verdict: Ship it! The implementation is solid, well-tested, and addresses a real security concern.

} else {
result.pluginsWithoutDeclarations.push(pkgName);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing type validation for pluginParameters from package.json

Low Severity

The processPluginDeclarations function accepts declarations typed as Record<string, EnvVarConfig> | null, but the value comes from JSON parsing with no runtime type check. If a package.json has pluginParameters set to a non-object value (like a string or array), Object.keys() and Object.entries() will process it incorrectly—for example, Object.keys("hello") returns ['0', '1', '2', '3', '4'], adding nonsensical index keys to allowedVars.

Additional Locations (1)

Fix in Cursor Fix in Web

@0xbbjoker 0xbbjoker merged commit d271ffb into develop Jan 15, 2026
22 of 23 checks passed
@0xbbjoker 0xbbjoker deleted the fix/env-variable-leakage-prevention branch January 15, 2026 15:11
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.

2 participants