From 2d607d36f674539bd88443fa73153e6fe8d1ca2c Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sun, 29 Mar 2026 20:57:57 -0700 Subject: [PATCH] fix(security): catch sensitive path writes in approval checks (#3859) Co-authored-by: Gutslabs --- tests/tools/test_approval.py | 35 +++++++++++++++++++++++++++++++++++ tools/approval.py | 18 +++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/tests/tools/test_approval.py b/tests/tools/test_approval.py index 0d48b7c1f..abdda05fa 100644 --- a/tests/tools/test_approval.py +++ b/tests/tools/test_approval.py @@ -339,6 +339,16 @@ class TestTeePattern: assert dangerous is True assert key is not None + def test_tee_custom_hermes_home_env(self): + dangerous, key, desc = detect_dangerous_command("echo x | tee $HERMES_HOME/.env") + assert dangerous is True + assert key is not None + + def test_tee_quoted_custom_hermes_home_env(self): + dangerous, key, desc = detect_dangerous_command('echo x | tee "$HERMES_HOME/.env"') + assert dangerous is True + assert key is not None + def test_tee_tmp_safe(self): dangerous, key, desc = detect_dangerous_command("echo hello | tee /tmp/output.txt") assert dangerous is False @@ -374,6 +384,30 @@ class TestFindExecFullPathRm: assert key is None +class TestSensitiveRedirectPattern: + """Detect shell redirection writes to sensitive user-managed paths.""" + + def test_redirect_to_custom_hermes_home_env(self): + dangerous, key, desc = detect_dangerous_command("echo x > $HERMES_HOME/.env") + assert dangerous is True + assert key is not None + + def test_append_to_home_ssh_authorized_keys(self): + dangerous, key, desc = detect_dangerous_command("cat key >> $HOME/.ssh/authorized_keys") + assert dangerous is True + assert key is not None + + def test_append_to_tilde_ssh_authorized_keys(self): + dangerous, key, desc = detect_dangerous_command("cat key >> ~/.ssh/authorized_keys") + assert dangerous is True + assert key is not None + + def test_redirect_to_safe_tmp_file(self): + dangerous, key, desc = detect_dangerous_command("echo hello > /tmp/output.txt") + assert dangerous is False + assert key is None + + class TestPatternKeyUniqueness: """Bug: pattern_key is derived by splitting on \\b and taking [1], so patterns starting with the same word (e.g. find -exec rm and find -delete) @@ -606,3 +640,4 @@ class TestNormalizationBypass: dangerous, key, desc = detect_dangerous_command(cmd) assert dangerous is False + diff --git a/tools/approval.py b/tools/approval.py index ee74b1134..8ae52407f 100644 --- a/tools/approval.py +++ b/tools/approval.py @@ -18,6 +18,21 @@ from typing import Optional logger = logging.getLogger(__name__) +# Sensitive write targets that should trigger approval even when referenced +# via shell expansions like $HOME or $HERMES_HOME. +_SSH_SENSITIVE_PATH = r'(?:~|\$home|\$\{home\})/\.ssh(?:/|$)' +_HERMES_ENV_PATH = ( + r'(?:~\/\.hermes/|' + r'(?:\$home|\$\{home\})/\.hermes/|' + r'(?:\$hermes_home|\$\{hermes_home\})/)' + r'\.env\b' +) +_SENSITIVE_WRITE_TARGET = ( + r'(?:/etc/|/dev/sd|' + rf'{_SSH_SENSITIVE_PATH}|' + rf'{_HERMES_ENV_PATH})' +) + # ========================================================================= # Dangerous command patterns # ========================================================================= @@ -46,7 +61,8 @@ DANGEROUS_PATTERNS = [ (r'\b(python[23]?|perl|ruby|node)\s+-[ec]\s+', "script execution via -e/-c flag"), (r'\b(curl|wget)\b.*\|\s*(ba)?sh\b', "pipe remote content to shell"), (r'\b(bash|sh|zsh|ksh)\s+<\s*>?\s*["\']?{_SENSITIVE_WRITE_TARGET}', "overwrite system file via redirection"), (r'\bxargs\s+.*\brm\b', "xargs with rm"), (r'\bfind\b.*-exec\s+(/\S*/)?rm\b', "find -exec rm"), (r'\bfind\b.*-delete\b', "find -delete"),