fix(cron): sanitize env for job script subprocesses

Cron no_agent and pre-check scripts ran with the full gateway/agent
environment, allowing scripts under HERMES_HOME/scripts/ to read provider
credentials. Apply _sanitize_subprocess_env like terminal and MCP paths
(SECURITY.md section 2.3).

Add regression test asserting blocklisted provider vars are absent in the
child process.
This commit is contained in:
0z1-ghb 2026-06-19 18:29:02 +03:00 committed by kshitijk4poor
parent c06898098b
commit da7253215d
2 changed files with 30 additions and 0 deletions

View file

@ -961,6 +961,10 @@ def _run_job_script(script_path: str) -> tuple[bool, str]:
Shell support lets ``no_agent=True`` jobs ship classic bash watchdogs
(the `memory-watchdog.sh` pattern) without wrapping them in Python.
Subprocess environment is passed through ``_sanitize_subprocess_env`` so
provider credentials and other Hermes-managed secrets are not inherited
(SECURITY.md §2.3), matching terminal and MCP child processes.
Args:
script_path: Path to the script. Relative paths are resolved
against HERMES_HOME/scripts/. Absolute and ~-prefixed paths
@ -1022,6 +1026,8 @@ def _run_job_script(script_path: str) -> tuple[bool, str]:
argv = [sys.executable, str(path)]
try:
from tools.environments.local import _sanitize_subprocess_env
popen_kwargs = {"creationflags": windows_hide_flags()} if sys.platform == "win32" else {}
result = subprocess.run(
argv,
@ -1029,6 +1035,7 @@ def _run_job_script(script_path: str) -> tuple[bool, str]:
text=True,
timeout=script_timeout,
cwd=str(path.parent),
env=_sanitize_subprocess_env(os.environ),
**popen_kwargs,
)
stdout = (result.stdout or "").strip()

View file

@ -132,6 +132,29 @@ class TestRunJobScript:
assert "exited with code 1" in output
assert "error info" in output
def test_script_subprocess_env_sanitized(self, cron_env, monkeypatch):
"""Cron scripts must not inherit Hermes provider env (SECURITY.md §2.3)."""
from tools.environments.local import _HERMES_PROVIDER_ENV_BLOCKLIST
from cron.scheduler import _run_job_script
blocked_var = next(iter(_HERMES_PROVIDER_ENV_BLOCKLIST))
monkeypatch.setenv(blocked_var, "must_not_leak")
script = cron_env / "scripts" / "env_probe.py"
script.write_text(
textwrap.dedent(
f"""\
import os
key = {blocked_var!r}
print("PRESENT" if os.environ.get(key) else "ABSENT")
"""
)
)
success, output = _run_job_script("env_probe.py")
assert success is True
assert output == "ABSENT"
def test_script_empty_output(self, cron_env):
from cron.scheduler import _run_job_script