fix(approval): detect absolute home shell rc writes

This commit is contained in:
helix4u 2026-06-13 14:36:47 -06:00 committed by Teknium
parent da28d5d113
commit abd69b8117
2 changed files with 67 additions and 0 deletions

View file

@ -369,6 +369,12 @@ class TestTeePattern:
assert dangerous is True
assert key is not None
def test_tee_absolute_home_bashrc(self):
bashrc = Path.home() / ".bashrc"
dangerous, key, desc = detect_dangerous_command(f"echo x | tee {bashrc}")
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
@ -560,11 +566,37 @@ class TestSensitiveRedirectPattern:
assert dangerous is True
assert key is not None
def test_append_to_absolute_home_ssh_authorized_keys(self):
authorized_keys = Path.home() / ".ssh" / "authorized_keys"
dangerous, key, desc = detect_dangerous_command(f"cat key >> {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_absolute_home_bashrc(self):
bashrc = Path.home() / ".bashrc"
dangerous, key, desc = detect_dangerous_command(f"echo 'alias ll=\"ls -la\"' > {bashrc}")
assert dangerous is True
assert key is not None
def test_redirect_to_home_set_after_import(self, monkeypatch, tmp_path):
late_home = tmp_path / "late-home"
late_home.mkdir()
monkeypatch.setenv("HOME", str(late_home))
dangerous, key, desc = detect_dangerous_command(f"echo x > {late_home}/.bashrc")
assert dangerous is True
assert key is not None
def test_redirect_to_other_absolute_home_bashrc_is_not_current_user_sensitive(self):
dangerous, key, desc = detect_dangerous_command("echo x > /tmp/not-current-home/.bashrc")
assert dangerous is False
assert key is None
def test_redirect_to_safe_tmp_file(self):
dangerous, key, desc = detect_dangerous_command("echo hello > /tmp/output.txt")
assert dangerous is False

View file

@ -561,6 +561,11 @@ def _normalize_command_for_detection(command: str) -> str:
command = re.sub(r'\\([^\n])', r'\1', command)
# Strip empty-string literals that split tokens: r''m → rm, r"\"m → rm.
command = re.sub(r"''|\"\"", '', command)
# Fold the current user's resolved absolute home path into ~/ at detection
# time so static user-sensitive patterns catch /home/alice/.bashrc the same
# way they catch ~/.bashrc. Do not snapshot this at import time: tests and
# profile/session launchers can set HOME after this module is imported.
command = _rewrite_resolved_user_home(command)
# Fold the resolved absolute active-profile home path into the canonical
# ~/.hermes/ form so the Hermes config/env patterns catch it. In Docker and
# gateway deployments the agent often references the resolved absolute path
@ -572,6 +577,36 @@ def _normalize_command_for_detection(command: str) -> str:
return command
def _rewrite_resolved_user_home(command: str) -> str:
"""Rewrite the current user's absolute home prefix to ``~/``.
Resolves HOME at detection time, including its symlink-resolved form, so
terminal commands targeting absolute home paths are checked by the same
static patterns as tilde and $HOME forms. No-op when HOME is unset or
degenerate.
"""
try:
home = os.path.expanduser("~")
candidates = [
home.rstrip("/"),
os.path.realpath(home).rstrip("/"),
]
except Exception:
return command
seen: set[str] = set()
for path in candidates:
if not path or path in seen:
continue
seen.add(path)
# Require an absolute path below root so a bad HOME cannot rewrite the
# whole filesystem namespace.
normalized = path.rstrip("/")
if not normalized.startswith("/") or normalized.count("/") < 2:
continue
command = command.replace(normalized + "/", "~/")
return command
def _rewrite_resolved_hermes_home(command: str) -> str:
"""Rewrite the resolved absolute Hermes home prefix to ``~/.hermes/``.