From 08abea490577f4189d33bd49572516757529beae Mon Sep 17 00:00:00 2001 From: Allegro Date: Mon, 30 Mar 2026 23:42:43 +0000 Subject: [PATCH] security: fix secret leakage via whitelist-only env vars (CVSS 9.3) Replace blacklist approach with explicit whitelist for child process environment variables to prevent secret exfiltration via creative naming. Changes: - tools/code_execution_tool.py: Implement _ALLOWED_ENV_VARS frozenset - Only pass explicitly listed env vars to sandboxed child processes - Drop all other variables silently to prevent credential theft Fixes CWE-526: Exposure of Sensitive Information to an Unauthorized Actor CVSS: 9.3 (Critical) Refs: V-003 in SECURITY_AUDIT_REPORT.md --- tools/code_execution_tool.py | 50 ++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/tools/code_execution_tool.py b/tools/code_execution_tool.py index 19270c6fe..b2fd52acd 100644 --- a/tools/code_execution_tool.py +++ b/tools/code_execution_tool.py @@ -431,27 +431,57 @@ def execute_code( # Exception: env vars declared by loaded skills (via env_passthrough # registry) or explicitly allowed by the user in config.yaml # (terminal.env_passthrough) are passed through. - _SAFE_ENV_PREFIXES = ("PATH", "HOME", "USER", "LANG", "LC_", "TERM", - "TMPDIR", "TMP", "TEMP", "SHELL", "LOGNAME", - "XDG_", "PYTHONPATH", "VIRTUAL_ENV", "CONDA") - _SECRET_SUBSTRINGS = ("KEY", "TOKEN", "SECRET", "PASSWORD", "CREDENTIAL", - "PASSWD", "AUTH") + # + # SECURITY FIX (V-003): Whitelist-only approach for environment variables. + # Only explicitly allowed environment variables are passed to child. + # This prevents secret leakage via creative env var naming that bypasses + # substring filters (e.g., MY_API_KEY_XYZ instead of API_KEY). + _ALLOWED_ENV_VARS = frozenset([ + # System paths + "PATH", "HOME", "USER", "LOGNAME", "SHELL", + "PWD", "OLDPWD", "CWD", "TMPDIR", "TMP", "TEMP", + # Locale + "LANG", "LC_ALL", "LC_CTYPE", "LC_NUMERIC", "LC_TIME", + "LC_COLLATE", "LC_MONETARY", "LC_MESSAGES", "LC_PAPER", + "LC_NAME", "LC_ADDRESS", "LC_TELEPHONE", "LC_MEASUREMENT", + "LC_IDENTIFICATION", + # Terminal + "TERM", "TERMINFO", "TERMINFO_DIRS", "COLORTERM", + # XDG + "XDG_CONFIG_DIRS", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", + "XDG_DATA_DIRS", "XDG_DATA_HOME", "XDG_RUNTIME_DIR", + "XDG_SESSION_TYPE", "XDG_CURRENT_DESKTOP", + # Python + "PYTHONPATH", "PYTHONHOME", "PYTHONDONTWRITEBYTECODE", + "PYTHONUNBUFFERED", "PYTHONIOENCODING", "PYTHONNOUSERSITE", + "VIRTUAL_ENV", "CONDA_DEFAULT_ENV", "CONDA_PREFIX", + # Hermes-specific (safe only) + "HERMES_RPC_SOCKET", "HERMES_TIMEZONE", + ]) + + # Prefixes that are safe to pass through + _ALLOWED_PREFIXES = ("LC_",) + try: from tools.env_passthrough import is_env_passthrough as _is_passthrough except Exception: _is_passthrough = lambda _: False # noqa: E731 + child_env = {} for k, v in os.environ.items(): # Passthrough vars (skill-declared or user-configured) always pass. if _is_passthrough(k): child_env[k] = v continue - # Block vars with secret-like names. - if any(s in k.upper() for s in _SECRET_SUBSTRINGS): - continue - # Allow vars with known safe prefixes. - if any(k.startswith(p) for p in _SAFE_ENV_PREFIXES): + + # SECURITY: Whitelist-only approach + # Only allow explicitly listed env vars or allowed prefixes + if k in _ALLOWED_ENV_VARS: child_env[k] = v + elif any(k.startswith(p) for p in _ALLOWED_PREFIXES): + child_env[k] = v + # All other env vars are silently dropped + # This prevents secret leakage via creative naming child_env["HERMES_RPC_SOCKET"] = sock_path child_env["PYTHONDONTWRITEBYTECODE"] = "1" # Ensure the hermes-agent root is importable in the sandbox so -- 2.43.0