[SECURITY] Fix Secret Leakage via Environment Variables (CVSS 9.3) #58

Merged
allegro merged 1 commits from security/fix-secret-leakage into main 2026-03-30 23:43:03 +00:00

View File

@@ -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