[SECURITY] Fix Secret Leakage via Environment Variables (CVSS 9.3) #58
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user