From 2b67e96aec2aa2abd5e94b544cda8e564c75f9f5 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 13 Jun 2026 13:53:18 -0700 Subject: [PATCH] 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. --- tests/tools/test_approval.py | 36 ++++++++++++++++++++++++++++++++++++ tools/approval.py | 12 ++++++++++++ 2 files changed, 48 insertions(+) diff --git a/tests/tools/test_approval.py b/tests/tools/test_approval.py index 683e621b044..b37d57555fb 100644 --- a/tests/tools/test_approval.py +++ b/tests/tools/test_approval.py @@ -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") diff --git a/tools/approval.py b/tools/approval.py index ce6be6e8f98..6ed1fb9cbcd 100644 --- a/tools/approval.py +++ b/tools/approval.py @@ -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