Compare commits

...

2 Commits

Author SHA1 Message Date
teknium1
0880e8ad78 test(batch): cover approval-guard enforcement for #35164
Asserts batch_runner sets the cron-session marker and that a flagged
dangerous command is blocked under the default cron deny policy.
2026-06-26 00:58:08 -07:00
liuhao1024
f3b79200ff fix(batch): set HERMES_CRON_SESSION to enforce approval guards
When batch_runner.py processes dataset prompts, it creates AIAgent
instances without setting any of the interactive environment variables
(HERMES_INTERACTIVE, HERMES_GATEWAY_SESSION, HERMES_EXEC_ASK). This
causes the dangerous command approval system to auto-approve all
flagged commands, allowing prompt injection payloads in untrusted
datasets to achieve arbitrary command execution.

Fix: set HERMES_CRON_SESSION=1 via os.environ.setdefault() before
creating AIAgent instances, so the approval system enforces the
cron-style deny-by-default policy.

Fixes #35164
2026-06-26 00:55:51 -07:00
2 changed files with 64 additions and 0 deletions

View File

@@ -314,6 +314,15 @@ def _process_single_prompt(
print(f" Prompt {prompt_index}: Using container image {container_image}")
try:
# Mark this process as a non-interactive batch session so the
# dangerous-command approval system (tools/approval.py) enforces
# the cron-style deny-by-default policy instead of auto-approving
# every command. Without this, batch_runner.py would bypass all
# safety checks because none of HERMES_INTERACTIVE,
# HERMES_GATEWAY_SESSION, or HERMES_EXEC_ASK are set.
# See: https://github.com/NousResearch/hermes-agent/issues/35164
os.environ.setdefault("HERMES_CRON_SESSION", "1")
# Sample toolsets from distribution for this prompt
selected_toolsets = sample_toolsets_from_distribution(config["distribution"])

View File

@@ -0,0 +1,55 @@
"""Regression test for #35164: batch_runner must not bypass the dangerous-command
approval guard.
batch_runner.py runs non-interactively and sets none of HERMES_INTERACTIVE /
HERMES_GATEWAY_SESSION / HERMES_EXEC_ASK, so check_all_command_guards() used to
fall through to its non-interactive auto-approve path and execute every flagged
dangerous command (arbitrary command execution from prompt-injected JSONL
datasets). The fix marks the batch process as a cron-style session
(HERMES_CRON_SESSION=1) so the cron deny-by-default policy applies.
"""
import os
import tools.approval as ap
def _clear_session_env(monkeypatch):
for var in ("HERMES_INTERACTIVE", "HERMES_GATEWAY_SESSION", "HERMES_EXEC_ASK"):
monkeypatch.delenv(var, raising=False)
class TestBatchRunnerApprovalGuard:
def test_no_cron_env_auto_approves_dangerous(self, monkeypatch):
"""Without the cron session marker, the non-interactive path approves —
this is the pre-fix bug state, kept as the contrast case."""
_clear_session_env(monkeypatch)
monkeypatch.delenv("HERMES_CRON_SESSION", raising=False)
monkeypatch.setattr(ap, "_YOLO_MODE_FROZEN", False)
monkeypatch.setattr(ap, "is_current_session_yolo_enabled", lambda: False)
monkeypatch.setattr(ap, "_get_approval_mode", lambda: "manual")
res = ap.check_all_command_guards("rm -rf /important/data", env_type="local")
assert res["approved"] is True
def test_cron_session_blocks_dangerous_under_deny(self, monkeypatch):
"""With HERMES_CRON_SESSION=1 (what batch_runner now sets) and the
default cron deny mode, a flagged dangerous command is blocked."""
_clear_session_env(monkeypatch)
monkeypatch.setenv("HERMES_CRON_SESSION", "1")
monkeypatch.setattr(ap, "_YOLO_MODE_FROZEN", False)
monkeypatch.setattr(ap, "is_current_session_yolo_enabled", lambda: False)
monkeypatch.setattr(ap, "_get_approval_mode", lambda: "manual")
monkeypatch.setattr(ap, "_get_cron_approval_mode", lambda: "deny")
res = ap.check_all_command_guards("rm -rf /important/data", env_type="local")
assert res["approved"] is False
assert "BLOCKED" in (res["message"] or "")
def test_batch_runner_sets_cron_session_marker(self):
"""_process_single_prompt sets HERMES_CRON_SESSION before constructing
the agent. Asserted at source level so the marker can't be silently
dropped in a refactor."""
import inspect
import batch_runner
src = inspect.getsource(batch_runner._process_single_prompt)
assert 'os.environ.setdefault("HERMES_CRON_SESSION"' in src