From 8641f4acf1225d58e838fd541e27fb64b596e49e Mon Sep 17 00:00:00 2001 From: AhmetArif0 <147827411+AhmetArif0@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:25:30 +0300 Subject: [PATCH] fix(approval): gate perl/ruby -i in-place edits of Hermes config/env MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sed -i coverage for ~/.hermes/config.yaml and .env was added in #14639, but perl -i and ruby -i — which perform the same direct file mutation — were not covered. The existing perl/ruby pattern only catches -e/-c (code evaluation), not -i (file mutation), so: perl -i -pe 's/approvals.mode: on/approvals.mode: off/' ~/.hermes/config.yaml bypasses the approval gate entirely, letting the agent flip approvals.mode off mid-session via the mtime-keyed config cache reload. Add a single pattern mirroring the sed -i lines: `\b(?:perl|ruby)\s+-[^\s]*i` against both _HERMES_CONFIG_PATH and _HERMES_ENV_PATH. Three regression tests pin the new coverage. --- tests/tools/test_approval.py | 21 +++++++++++++++++++++ tools/approval.py | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/tests/tools/test_approval.py b/tests/tools/test_approval.py index fb1ef72cdc0c6..b159caa3d1458 100644 --- a/tests/tools/test_approval.py +++ b/tests/tools/test_approval.py @@ -428,6 +428,27 @@ def test_custom_hermes_home(self): dangerous, key, desc = detect_dangerous_command("echo x | tee $HERMES_HOME/config.yaml") assert dangerous is True + def test_perl_in_place_config(self): + # perl -i performs the same in-place mutation as sed -i but was not + # caught by the -e/-c pattern (which targets code evaluation). + dangerous, key, desc = detect_dangerous_command( + "perl -i -pe 's/approvals.mode: on/approvals.mode: off/' ~/.hermes/config.yaml" + ) + assert dangerous is True + assert "in-place" in desc.lower() or "perl" in desc.lower() + + def test_ruby_in_place_config(self): + dangerous, key, desc = detect_dangerous_command( + "ruby -i -pe 'gsub(/manual/, \"off\")' ~/.hermes/config.yaml" + ) + assert dangerous is True + + def test_perl_in_place_env(self): + dangerous, key, desc = detect_dangerous_command( + "perl -i -pe 's/SECRET=old/SECRET=new/' ~/.hermes/.env" + ) + assert dangerous is True + def test_read_is_safe(self): # Reading config is not a write — must not trip. dangerous, key, desc = detect_dangerous_command("cat ~/.hermes/config.yaml") diff --git a/tools/approval.py b/tools/approval.py index 47f4a5f443160..22d88428bab87 100644 --- a/tools/approval.py +++ b/tools/approval.py @@ -412,6 +412,10 @@ def _sudo_stdin_block_result(description: str) -> dict: # the terminal side is not an open door. See #14639. (rf'\bsed\s+-[^\s]*i.*(?:{_HERMES_CONFIG_PATH}|{_HERMES_ENV_PATH})', "in-place edit of Hermes config/env"), (rf'\bsed\s+--in-place\b.*(?:{_HERMES_CONFIG_PATH}|{_HERMES_ENV_PATH})', "in-place edit of Hermes config/env (long flag)"), + # perl -i and ruby -i perform the same in-place mutation as sed -i but are + # not caught by the -e/-c script-execution pattern above (which targets code + # evaluation, not file mutation). Pairs the sed -i coverage from #14639. + (rf'\b(?:perl|ruby)\s+-[^\s]*i.*(?:{_HERMES_CONFIG_PATH}|{_HERMES_ENV_PATH})', "in-place edit of Hermes config/env (perl/ruby)"), # Script execution via heredoc — bypasses the -e/-c flag patterns above. # `python3 << 'EOF'` feeds arbitrary code via stdin without -c/-e flags. (r'\b(python[23]?|perl|ruby|node)\s+<<', "script execution via heredoc"),