From 588050a100168e1a383dde54eb5cba160039247c Mon Sep 17 00:00:00 2001 From: memosr Date: Sat, 4 Apr 2026 22:09:32 +0300 Subject: [PATCH 1/2] fix(security): guard cron script against path traversal and redact output --- cron/scheduler.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cron/scheduler.py b/cron/scheduler.py index b014799837e39..7fa762a80a0f4 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -248,7 +248,15 @@ def _run_job_script(script_path: str) -> tuple[bool, str]: path = Path(script_path).expanduser() if not path.is_absolute(): # Resolve relative paths against HERMES_HOME/scripts/ - path = get_hermes_home() / "scripts" / path + scripts_dir = get_hermes_home() / "scripts" + path = (scripts_dir / path).resolve() + # Guard against path traversal (e.g. "../../etc/passwd") + try: + path.relative_to(scripts_dir.resolve()) + except ValueError: + return False, f"Script path escapes the scripts directory: {script_path!r}" + else: + path = path.resolve() if not path.exists(): return False, f"Script not found: {path}" @@ -274,6 +282,13 @@ def _run_job_script(script_path: str) -> tuple[bool, str]: parts.append(f"stdout:\n{stdout}") return False, "\n".join(parts) + # Redact any secrets that may appear in script output before + # they are injected into the LLM prompt context. + try: + from agent.redact import redact_sensitive_text + stdout = redact_sensitive_text(stdout) + except Exception: + pass return True, stdout except subprocess.TimeoutExpired: From 6a12e320da2498a61b1ecf169995ae41fe3f053a Mon Sep 17 00:00:00 2001 From: memosr Date: Sat, 4 Apr 2026 23:16:49 +0300 Subject: [PATCH 2/2] fix(security): restrict absolute cron script paths to HERMES_HOME --- cron/scheduler.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cron/scheduler.py b/cron/scheduler.py index 7fa762a80a0f4..7a6ec89d15a15 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -257,6 +257,12 @@ def _run_job_script(script_path: str) -> tuple[bool, str]: return False, f"Script path escapes the scripts directory: {script_path!r}" else: path = path.resolve() + # Restrict absolute paths to HERMES_HOME to prevent + # arbitrary file execution outside the user's data directory. + try: + path.relative_to(get_hermes_home().resolve()) + except ValueError: + return False, f"Absolute script path must be inside HERMES_HOME: {script_path!r}" if not path.exists(): return False, f"Script not found: {path}"