fix: align webui runtime env handling with gateway behavior
This commit is contained in:
1 parent
bc11cfe6d2
commit
dfcc06acdb
3 files changed
+80
-6
No files matched your search
+38
-3
@@ -696,6 +696,40 @@ def get_profile_runtime_env(home: Path) -> dict[str, str]:
|
||||
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
|
||||
def profile_env_for_background_worker(
|
||||
session,
|
||||
@@ -724,6 +758,7 @@ def profile_env_for_background_worker(
|
||||
|
||||
profile_home_path = Path(get_hermes_home_for_profile(profile))
|
||||
runtime_env = get_profile_runtime_env(profile_home_path)
|
||||
safe_runtime_env = filter_runtime_env_for_gateway_parity(runtime_env)
|
||||
except Exception:
|
||||
log.debug(
|
||||
"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
|
||||
return
|
||||
|
||||
thread_env = dict(runtime_env)
|
||||
thread_env = dict(safe_runtime_env)
|
||||
thread_env["HERMES_HOME"] = str(profile_home_path)
|
||||
# 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
|
||||
@@ -749,11 +784,11 @@ def profile_env_for_background_worker(
|
||||
try:
|
||||
_set_thread_env(**thread_env)
|
||||
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
|
||||
old_hermes_home = os.environ.get("HERMES_HOME")
|
||||
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)
|
||||
try:
|
||||
patch_skill_home_modules(profile_home_path)
|
||||
|
||||
+5
-2
@@ -5856,6 +5856,7 @@ def _run_agent_streaming(
|
||||
# process-level active-profile global. Falls back gracefully.
|
||||
try:
|
||||
from api.profiles import (
|
||||
filter_runtime_env_for_gateway_parity,
|
||||
patch_skill_home_modules,
|
||||
get_hermes_home_for_profile,
|
||||
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 = str(_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:
|
||||
_profile_home = os.environ.get('HERMES_HOME', '')
|
||||
_profile_runtime_env = {}
|
||||
_safe_profile_runtime_env = {}
|
||||
patch_skill_home_modules = None
|
||||
|
||||
# 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
|
||||
# and preventing a deadlock where the restore would re-enter the same 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_exec_ask = os.environ.get('HERMES_EXEC_ASK')
|
||||
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_chat_id = os.environ.get('HERMES_SESSION_CHAT_ID')
|
||||
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['HERMES_EXEC_ASK'] = '1'
|
||||
os.environ['HERMES_SESSION_KEY'] = session_id
|
||||
|
||||
@@ -52,8 +52,44 @@ def test_streaming_applies_profile_runtime_env_to_agent_run():
|
||||
|
||||
assert "get_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 "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():
|
||||
|
||||
Reference in new issue
Block a user