mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-20 15:33:54 +00:00
fix(approval): allow verifier temp cleanup
This commit is contained in:
parent
f67aae3230
commit
0c8bcd3399
4 changed files with 95 additions and 1 deletions
|
|
@ -289,7 +289,7 @@ def build_verify_on_stop_nudge(
|
|||
+ "), read any failure, repair the code, and summarize what passed."
|
||||
)
|
||||
else:
|
||||
temp_dir = tempfile.gettempdir()
|
||||
temp_dir = os.path.realpath(tempfile.gettempdir())
|
||||
command_instruction = (
|
||||
"No canonical test/lint/build command was detected. Create a focused "
|
||||
f"temporary verification script under `{temp_dir}` using an OS-safe "
|
||||
|
|
|
|||
|
|
@ -260,6 +260,27 @@ def test_no_suite_nudge_requests_temp_script(tmp_path, monkeypatch):
|
|||
assert "creative UI/visual work" in nudge
|
||||
|
||||
|
||||
def test_no_suite_nudge_uses_canonical_temp_dir(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
|
||||
project = tmp_path / "project"
|
||||
project.mkdir()
|
||||
(project / "package.json").write_text("{}", encoding="utf-8")
|
||||
real_temp = tmp_path / "real-temp"
|
||||
real_temp.mkdir()
|
||||
linked_temp = tmp_path / "linked-temp"
|
||||
linked_temp.symlink_to(real_temp, target_is_directory=True)
|
||||
monkeypatch.setattr(tempfile, "gettempdir", lambda: str(linked_temp))
|
||||
|
||||
nudge = build_verify_on_stop_nudge(
|
||||
session_id="s1",
|
||||
changed_paths=[str(project / "src" / "app.ts")],
|
||||
)
|
||||
|
||||
assert nudge is not None
|
||||
assert str(real_temp) in nudge
|
||||
assert str(linked_temp) not in nudge
|
||||
|
||||
|
||||
def test_verify_guidance_can_be_disabled(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
|
||||
_node_project(tmp_path)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import ast
|
||||
import os
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
|
@ -116,6 +117,53 @@ class TestDetectDangerousRm:
|
|||
assert key is not None
|
||||
assert "delete" in desc.lower()
|
||||
|
||||
def test_nonrecursive_verification_artifact_cleanup_is_not_dangerous(self):
|
||||
with mock_patch("tempfile.gettempdir", return_value="/tmp"):
|
||||
for prefix in ("hermes-verify-", "hermes-ad-hoc-"):
|
||||
assert detect_dangerous_command(f"rm -f /tmp/{prefix}example.py") == (
|
||||
False,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
||||
def test_symlinked_temp_dir_only_exempts_canonical_target(self, tmp_path):
|
||||
real_temp = tmp_path / "real-temp"
|
||||
real_temp.mkdir()
|
||||
linked_temp = tmp_path / "linked-temp"
|
||||
linked_temp.symlink_to(real_temp, target_is_directory=True)
|
||||
basename = "hermes-verify-example.py"
|
||||
|
||||
with mock_patch("tempfile.gettempdir", return_value=str(linked_temp)):
|
||||
assert detect_dangerous_command(f"rm -f {linked_temp / basename}")[0] is True
|
||||
assert detect_dangerous_command(f"rm -f {real_temp / basename}") == (
|
||||
False,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
||||
def test_verification_cleanup_exemption_rejects_broader_deletions(self):
|
||||
commands = (
|
||||
"rm -rf /tmp/hermes-verify-example.py",
|
||||
"rm -f /tmp/hermes-verify-example.py /tmp/other.py",
|
||||
"rm -f /tmp/nested/hermes-verify-example.py",
|
||||
"rm -f /tmp/nested/../hermes-verify-example.py",
|
||||
"rm -f /tmp/./hermes-verify-example.py",
|
||||
"rm -f /tmp//hermes-verify-example.py",
|
||||
"rm -f /tmp/a/../../tmp/hermes-verify-example.py",
|
||||
"rm -f /var/tmp/hermes-verify-example.py",
|
||||
"rm -f /tmp/unrelated.py",
|
||||
"rm -f /tmp/hermes-verify-*",
|
||||
"rm -f /tmp/hermes-verify-$(touch>/tmp/pwned).py",
|
||||
"rm -f /tmp/hermes-ad-hoc-`touch>/tmp/pwned`.py",
|
||||
"rm -f /tmp/hermes-verify-example.py; touch /tmp/pwned",
|
||||
)
|
||||
with mock_patch("tempfile.gettempdir", return_value="/tmp"):
|
||||
for command in commands:
|
||||
is_dangerous, key, desc = detect_dangerous_command(command)
|
||||
assert is_dangerous is True, command
|
||||
assert key is not None, command
|
||||
assert "delete" in desc.lower(), command
|
||||
|
||||
|
||||
class TestWindowsShellDestructiveCommands:
|
||||
def test_cmd_del_requires_approval(self):
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import os
|
|||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import unicodedata
|
||||
|
|
@ -1383,12 +1384,36 @@ def _command_detection_variants(command: str):
|
|||
yield variant
|
||||
|
||||
|
||||
def _is_verification_artifact_cleanup(command: str) -> bool:
|
||||
"""Return whether *command* only removes one Hermes ad-hoc temp script."""
|
||||
try:
|
||||
argv = shlex.split(command, posix=True)
|
||||
except ValueError:
|
||||
return False
|
||||
if len(argv) != 3 or argv[0] != "rm" or argv[1] != "-f":
|
||||
return False
|
||||
|
||||
operand = argv[2]
|
||||
temp_dir = os.path.realpath(tempfile.gettempdir())
|
||||
basename = os.path.basename(operand)
|
||||
if operand != os.path.join(temp_dir, basename):
|
||||
return False
|
||||
|
||||
target = os.path.realpath(operand)
|
||||
if os.path.dirname(target) != temp_dir:
|
||||
return False
|
||||
return re.fullmatch(r"hermes-(?:verify|ad-hoc)-[A-Za-z0-9_.-]+", basename) is not None
|
||||
|
||||
|
||||
def detect_dangerous_command(command: str) -> tuple:
|
||||
"""Check if a command matches any dangerous patterns.
|
||||
|
||||
Returns:
|
||||
(is_dangerous, pattern_key, description) or (False, None, None)
|
||||
"""
|
||||
if _is_verification_artifact_cleanup(command):
|
||||
return (False, None, None)
|
||||
|
||||
for command_variant in _command_detection_variants(command):
|
||||
command_lower = command_variant.lower()
|
||||
for pattern_re, description in DANGEROUS_PATTERNS_COMPILED:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue