NousResearch/hermes-agent

Approval regex over-flags absolute paths as dangerous deletes

Cluster 18083 · agent. Data as of

2 open items. Fix merged in #63120 on 12 Jul 2026. Check it covers your case.

The short answer

1open issue
1open PR
9items grouped
Known?
Yes. 9 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #63120. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #63120. That is separate from the fix event.
Still open
1 issue and 1 PR.

A DANGEROUS_PATTERNS regex incorrectly matches ordinary absolute paths as root-delete operations, prompting several fixes to tighten the pattern to genuine system-root targets and carve out exceptions for verifier and Hermes-owned temp file cleanup.

Open issues1

  • Issue#18083Over-broad DANGEROUS_PATTERNS regex flags every absolute path as 'delete in root path'

Open PRs1

  • PR#39629fix(approval): avoid flagging ordinary absolute paths as root deletes (#18083)

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #63120 fix(approval): allow verifier temp cleanup

    merged to main

    files: agent/verification_stop.py, scripts/release.py, tests/agent/test_verification_stop.py, tests/tools/test_approval.py, tools/approval.py

    Show the diff
    • agent/verification_stop.py
      @@ -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 "
    • scripts/release.py
      @@ -45,6 +45,7 @@
       
       # Auto-extracted from noreply emails + manual overrides
       AUTHOR_MAP = {
      +    "[email protected]": "ansel-f",  # PR #62388 salvage (approval: allow exact verifier temp cleanup without broadening rm safety boundary)
           "[redacted:email]": "professorpalmer",  # PR #48591 salvage (sessions: CLI workspace filter + restore-cwd-on-resume)
           "[redacted:email]": "Supersynergy",  # PR #59241 salvage (desktop: workspace path status-bar action)
           "[redacted:email]": "esthonjr",  # PR #61950 salvage (desktop: legacy non-git workspace grouping + Windows path identity)
    • tests/agent/test_verification_stop.py
      @@ -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)
    • tests/tools/test_approval.py
      @@ -2,6 +2,7 @@
       
       import ast
       import os
      +import tempfile
       import threading
       import time
       from pathlib import Path
      @@ -116,6 +117,53 @@ def test_rm_recursive_long_flag(self):
               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):
    • tools/approval.py
      @@ -17,6 +17,7 @@
       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:

5 PRs closed without merging

From the close comments: done elsewhere 2 · PR volume cleanup 1 · merged via another change 1 · superseded by another PR 1

  • #18089 fix(approval): tighten 'delete in root path' regex to system-root targets only · PR volume cleanup → #26829, #18083
  • #18533 fix(approval): tighten 'delete in root path' regex to system roots only · superseded by another PR → #18089, #12584, #12592
  • #62379 fix(tools): allow cleanup of Hermes-owned temp files without approval · done elsewhere → #63120, #62388
  • #62388 fix(approval): allow verifier temp cleanup · merged via another change → #63120
  • #62400 fix(agent): instruct background review fork to call skill_view before patching · done elsewhere → #60331, #62400, #62377
Closed issues · 1
  • Closed#62377Verify-on-stop mandates temp cleanup that dangerous-command approval blocks
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.20: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent
Merged PRs
#63120
Closed issues
1
Attempts
5 PRs closed without merging
Fix link checked
12 Jul 2026

For your agent

The same facts, typed.