# NousResearch/hermes-agent · Approval regex over-flags absolute paths as dangerous deletes

## Open now

- 1 issue, 1 PR open on GitHub.
- #18083: Over-broad DANGEROUS_PATTERNS regex flags every absolute path as 'delete in root path'
- PR #39629: fix(approval): avoid flagging ordinary absolute paths as root deletes (#18083)

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.

## TL;DR

- 9 member(s): 1 open issue(s), 1 closed issue(s), 1 open PR(s), 1 merged PR(s), 5 PR(s) closed without merging.
- A fix was merged to main in PR #63120. Verify it addresses your case.
- **Recommended:** read PR #63120 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-63120`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member 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.

## Open related issues

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

## Closed / duplicate issues

- #62377: Verify-on-stop mandates temp cleanup that dangerous-command approval blocks

## Prior & competing PRs

- **#18533: fix(approval): tighten 'delete in root path' regex to system roots only**. closed without merge · fix tier `suggested`
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#18089`, `#12584`, `#12592`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#63120: fix(approval): allow verifier temp cleanup**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `agent/verification_stop.py`, `scripts/release.py`, `tests/agent/test_verification_stop.py`, `tests/tools/test_approval.py`, `tools/approval.py`
  - fix hunks:
  - `agent/verification_stop.py`

    ```diff
    @@ -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`

    ```diff
    @@ -45,6 +45,7 @@
     
     # Auto-extracted from noreply emails + manual overrides
     AUTHOR_MAP = {
    +    "135129512+ansel-f@users.noreply.github.com": "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`

    ```diff
    @@ -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`

    ```diff
    @@ -2,6 +2,7 @@
     
     import ast
     import os
    +import tempfile
     import threading
     import time
     from pathlib import Path
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -17,6 +17,7 @@
     import re
     import shlex
     import sys
    +import tempfile
     import threading
     import time
     import unicodedata
    ```

    ```diff
    @@ -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:
    ```
- **#18089: fix(approval): tighten 'delete in root path' regex to system-root targets only**. closed without merge
  - maintainer direction: category `volume_quota`; implication `active`; refs `#26829`, `#18083`
- **#39629: fix(approval): avoid flagging ordinary absolute paths as root deletes (#18083)**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `tests/tools/test_approval.py`, `tools/approval.py`
- **#62379: fix(tools): allow cleanup of Hermes-owned temp files without approval**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#63120`, `#62388`
- **#62388: fix(approval): allow verifier temp cleanup**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#63120`
- **#62400: fix(agent): instruct background review fork to call skill_view before patching**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#60331`, `#62400`, `#62377`

## Fix status

- Merged to main in PR #63120 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-07-12`.
- Subsystem: `agent`
