fix(approval): gate in-place edits to sensitive user files

Cover sed, perl, and ruby in-place mutations against shell rc, SSH, and credential files so terminal approvals pair the redirection and copy guards.
This commit is contained in:
Teknium 2026-06-13 13:53:18 -07:00
parent abd69b8117
commit 2b67e96aec
2 changed files with 48 additions and 0 deletions

View file

@ -702,6 +702,42 @@ class TestSensitiveCopyMovePattern:
assert dangerous is False
class TestSensitiveInPlaceEditPattern:
"""Detect in-place edits to user startup and credential files."""
def test_sed_in_place_bashrc(self):
dangerous, key, desc = detect_dangerous_command("sed -i 's/a/b/' ~/.bashrc")
assert dangerous is True
assert key is not None
def test_sed_long_in_place_ssh_authorized_keys(self):
dangerous, key, desc = detect_dangerous_command(
"sed --in-place 's/key/newkey/' ~/.ssh/authorized_keys"
)
assert dangerous is True
assert key is not None
def test_perl_in_place_netrc(self):
dangerous, key, desc = detect_dangerous_command(
"perl -i -pe 's/pass/pass2/' ~/.netrc"
)
assert dangerous is True
assert key is not None
def test_ruby_in_place_absolute_home_zshrc(self):
zshrc = Path.home() / ".zshrc"
dangerous, key, desc = detect_dangerous_command(
f"ruby -i -pe 'gsub(/a/, \"b\")' {zshrc}"
)
assert dangerous is True
assert key is not None
def test_sed_in_place_regular_file_safe(self):
dangerous, key, desc = detect_dangerous_command("sed -i 's/a/b/' notes.txt")
assert dangerous is False
assert key is None
class TestProjectSensitiveTeePattern:
def test_tee_to_local_dotenv_requires_approval(self):
dangerous, key, desc = detect_dangerous_command("printenv | tee .env.local")

View file

@ -208,6 +208,11 @@ _SENSITIVE_WRITE_TARGET = (
rf'{_SHELL_RC_FILES}|'
rf'{_CREDENTIAL_FILES})'
)
_USER_SENSITIVE_WRITE_TARGET = (
rf'(?:{_SSH_SENSITIVE_PATH}|'
rf'{_SHELL_RC_FILES}|'
rf'{_CREDENTIAL_FILES})'
)
_PROJECT_SENSITIVE_WRITE_TARGET = rf'(?:{_PROJECT_ENV_PATH}|{_PROJECT_CONFIG_PATH})'
_COMMAND_TAIL = r'(?:\s*(?:&&|\|\||;).*)?$'
@ -455,6 +460,13 @@ DANGEROUS_PATTERNS = [
# The trailing `[^\s"\']*` consumes the rest of the destination filename
# (e.g. `authorized_keys` after the `~/.ssh/` fragment).
(rf'\b(cp|mv|install)\b.*\s["\']?{_SENSITIVE_WRITE_TARGET}[^\s"\']*["\']?{_COMMAND_TAIL}', "copy/move file into sensitive credential/SSH/shell-rc path"),
# In-place edits mutate the target file directly, bypassing redirection,
# tee, and copy/move/install coverage. Gate the same user-controlled
# startup/credential files so `sed -i ... ~/.bashrc` and `perl -i ...
# ~/.ssh/authorized_keys` cannot silently plant login commands or keys.
(rf'\bsed\s+-[^\s]*i.*(?:{_USER_SENSITIVE_WRITE_TARGET})[^\s"\']*', "in-place edit of sensitive credential/SSH/shell-rc path"),
(rf'\bsed\s+--in-place\b.*(?:{_USER_SENSITIVE_WRITE_TARGET})[^\s"\']*', "in-place edit of sensitive credential/SSH/shell-rc path (long flag)"),
(rf'\b(?:perl|ruby)\b.*(?:^|\s)-[^\s]*i\b.*(?:{_USER_SENSITIVE_WRITE_TARGET})[^\s"\']*', "in-place edit of sensitive credential/SSH/shell-rc path (perl/ruby)"),
(rf'\bsed\s+-[^\s]*i.*\s{_SYSTEM_CONFIG_PATH}', "in-place edit of system config"),
(rf'\bsed\s+--in-place\b.*\s{_SYSTEM_CONFIG_PATH}', "in-place edit of system config (long flag)"),
# In-place edit of a Hermes-managed security file (~/.hermes/config.yaml or