fix(security): catch sensitive path writes in approval checks (#3859)
Co-authored-by: Gutslabs <gutslabsxyz@gmail.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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*\(\s*(curl|wget)\b', "execute remote script via process substitution"),
|
||||
(r'\btee\b.*(/etc/|/dev/sd|\.ssh/|\.hermes/\.env)', "overwrite system file via tee"),
|
||||
(rf'\btee\b.*["\']?{_SENSITIVE_WRITE_TARGET}', "overwrite system file via tee"),
|
||||
(rf'>>?\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"),
|
||||
|
||||
Reference in New Issue
Block a user