fix: align webui runtime env handling with gateway behavior

This commit is contained in:
James committed 2026-06-18 19:01:17 +00:00
1 parent bc11cfe6d2
commit dfcc06acdb
3 files changed
+80 -6

No files matched your search

+38 -3
View File
@@ -696,6 +696,40 @@ def get_profile_runtime_env(home: Path) -> dict[str, str]:
return env return env
# Match Hermes Agent gateway behavior: profile-scoped WebUI runs should
# project intended runtime vars (credentials, HERMES_HOME, TERMINAL_*)
# without allowing profile env to override core shell identity variables
# like HOME or PATH.
_BLOCKED_RUNTIME_ENV_KEYS = {
'HOME',
'PATH',
'PWD',
'SHELL',
'USER',
'LOGNAME',
'SHLVL',
'OLDPWD',
'PYTHONPATH',
'VIRTUAL_ENV',
'LD_LIBRARY_PATH',
}
def filter_runtime_env_for_gateway_parity(env: dict[str, str]) -> dict[str, str]:
"""Return a profile runtime env filtered to mimic Hermes gateway semantics."""
filtered: dict[str, str] = {}
for key, value in (env or {}).items():
k = str(key).strip()
if not k:
continue
if k in _BLOCKED_RUNTIME_ENV_KEYS:
continue
if k.startswith('XDG_'):
continue
filtered[k] = value
return filtered
@contextmanager @contextmanager
def profile_env_for_background_worker( def profile_env_for_background_worker(
session, session,
@@ -724,6 +758,7 @@ def profile_env_for_background_worker(
profile_home_path = Path(get_hermes_home_for_profile(profile)) profile_home_path = Path(get_hermes_home_for_profile(profile))
runtime_env = get_profile_runtime_env(profile_home_path) runtime_env = get_profile_runtime_env(profile_home_path)
safe_runtime_env = filter_runtime_env_for_gateway_parity(runtime_env)
except Exception: except Exception:
log.debug( log.debug(
"Failed to resolve profile env for %s profile %s; falling back to current env", "Failed to resolve profile env for %s profile %s; falling back to current env",
@@ -734,7 +769,7 @@ def profile_env_for_background_worker(
yield yield
return return
thread_env = dict(runtime_env) thread_env = dict(safe_runtime_env)
thread_env["HERMES_HOME"] = str(profile_home_path) thread_env["HERMES_HOME"] = str(profile_home_path)
# Hybrid profile routing: keep the broad runtime env in WebUI's thread-local # Hybrid profile routing: keep the broad runtime env in WebUI's thread-local
# channel for WebUI helpers, and also mirror it into process env for the # channel for WebUI helpers, and also mirror it into process env for the
@@ -749,11 +784,11 @@ def profile_env_for_background_worker(
try: try:
_set_thread_env(**thread_env) _set_thread_env(**thread_env)
with _ENV_LOCK: with _ENV_LOCK:
old_runtime_env = {key: os.environ.get(key) for key in runtime_env} old_runtime_env = {key: os.environ.get(key) for key in safe_runtime_env}
had_hermes_home = "HERMES_HOME" in os.environ had_hermes_home = "HERMES_HOME" in os.environ
old_hermes_home = os.environ.get("HERMES_HOME") old_hermes_home = os.environ.get("HERMES_HOME")
skill_home_snapshot = snapshot_skill_home_modules() skill_home_snapshot = snapshot_skill_home_modules()
os.environ.update(runtime_env) os.environ.update(safe_runtime_env)
os.environ["HERMES_HOME"] = str(profile_home_path) os.environ["HERMES_HOME"] = str(profile_home_path)
try: try:
patch_skill_home_modules(profile_home_path) patch_skill_home_modules(profile_home_path)
+5 -2
View File
@@ -5856,6 +5856,7 @@ def _run_agent_streaming(
# process-level active-profile global. Falls back gracefully. # process-level active-profile global. Falls back gracefully.
try: try:
from api.profiles import ( from api.profiles import (
filter_runtime_env_for_gateway_parity,
patch_skill_home_modules, patch_skill_home_modules,
get_hermes_home_for_profile, get_hermes_home_for_profile,
get_profile_runtime_env, get_profile_runtime_env,
@@ -5863,9 +5864,11 @@ def _run_agent_streaming(
_profile_home_path = get_hermes_home_for_profile(getattr(s, 'profile', None)) _profile_home_path = get_hermes_home_for_profile(getattr(s, 'profile', None))
_profile_home = str(_profile_home_path) _profile_home = str(_profile_home_path)
_profile_runtime_env = get_profile_runtime_env(_profile_home_path) _profile_runtime_env = get_profile_runtime_env(_profile_home_path)
_safe_profile_runtime_env = filter_runtime_env_for_gateway_parity(_profile_runtime_env)
except ImportError: except ImportError:
_profile_home = os.environ.get('HERMES_HOME', '') _profile_home = os.environ.get('HERMES_HOME', '')
_profile_runtime_env = {} _profile_runtime_env = {}
_safe_profile_runtime_env = {}
patch_skill_home_modules = None patch_skill_home_modules = None
# Profile-aware provider/model enrichment: when the session belongs # Profile-aware provider/model enrichment: when the session belongs
@@ -5922,7 +5925,7 @@ def _run_agent_streaming(
# The finally block re-acquires to restore — keeping critical sections short # The finally block re-acquires to restore — keeping critical sections short
# and preventing a deadlock where the restore would re-enter the same lock. # and preventing a deadlock where the restore would re-enter the same lock.
with _ENV_LOCK: with _ENV_LOCK:
old_profile_env = {key: os.environ.get(key) for key in _profile_runtime_env} old_profile_env = {key: os.environ.get(key) for key in _safe_profile_runtime_env}
old_cwd = os.environ.get('TERMINAL_CWD') old_cwd = os.environ.get('TERMINAL_CWD')
old_exec_ask = os.environ.get('HERMES_EXEC_ASK') old_exec_ask = os.environ.get('HERMES_EXEC_ASK')
old_session_key = os.environ.get('HERMES_SESSION_KEY') old_session_key = os.environ.get('HERMES_SESSION_KEY')
@@ -5930,7 +5933,7 @@ def _run_agent_streaming(
old_session_platform = os.environ.get('HERMES_SESSION_PLATFORM') old_session_platform = os.environ.get('HERMES_SESSION_PLATFORM')
old_session_chat_id = os.environ.get('HERMES_SESSION_CHAT_ID') old_session_chat_id = os.environ.get('HERMES_SESSION_CHAT_ID')
old_hermes_home = os.environ.get('HERMES_HOME') old_hermes_home = os.environ.get('HERMES_HOME')
os.environ.update(_profile_runtime_env) os.environ.update(_safe_profile_runtime_env)
os.environ['TERMINAL_CWD'] = str(s.workspace) os.environ['TERMINAL_CWD'] = str(s.workspace)
os.environ['HERMES_EXEC_ASK'] = '1' os.environ['HERMES_EXEC_ASK'] = '1'
os.environ['HERMES_SESSION_KEY'] = session_id os.environ['HERMES_SESSION_KEY'] = session_id
+37 -1
View File
@@ -52,8 +52,44 @@ def test_streaming_applies_profile_runtime_env_to_agent_run():
assert "get_profile_runtime_env" in src assert "get_profile_runtime_env" in src
assert "_profile_runtime_env" in src assert "_profile_runtime_env" in src
assert "_safe_profile_runtime_env" in src
assert "old_profile_env" in src assert "old_profile_env" in src
assert "os.environ.update(_profile_runtime_env)" in src assert "filter_runtime_env_for_gateway_parity" in src
assert "os.environ.update(_safe_profile_runtime_env)" in src
assert "os.environ.update(_profile_runtime_env)" not in src
def test_filter_runtime_env_for_gateway_parity_blocks_shell_identity_vars():
from api.profiles import filter_runtime_env_for_gateway_parity
env = {
"HOME": "/tmp/fake-home",
"PATH": "/tmp/fake-bin:/usr/bin",
"PWD": "/tmp/fake-pwd",
"SHELL": "/bin/zsh",
"OPENAI_API_KEY": "test-key",
"TERMINAL_ENV": "ssh",
"TERMINAL_CWD": "/workspace",
}
filtered = filter_runtime_env_for_gateway_parity(env)
assert "HOME" not in filtered
assert "PATH" not in filtered
assert "PWD" not in filtered
assert "SHELL" not in filtered
assert filtered["OPENAI_API_KEY"] == "test-key"
assert filtered["TERMINAL_ENV"] == "ssh"
assert filtered["TERMINAL_CWD"] == "/workspace"
def test_profile_background_worker_uses_gateway_parity_runtime_env_filter():
src = Path("api/profiles.py").read_text(encoding="utf-8")
assert "filter_runtime_env_for_gateway_parity" in src
assert "safe_runtime_env" in src
assert "os.environ.update(safe_runtime_env)" in src
assert "os.environ.update(runtime_env)" not in src
def test_streaming_thread_env_allows_profile_terminal_cwd_override(): def test_streaming_thread_env_allows_profile_terminal_cwd_override():