fix(approval): allow verifier temp cleanup

This commit is contained in:
ansel-f 2026-07-11 09:05:44 +08:00 committed by Teknium
parent f67aae3230
commit 0c8bcd3399
4 changed files with 95 additions and 1 deletions

View file

@ -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 "

View file

@ -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)

View file

@ -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):

View file

@ -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: