mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-20 10:11:58 +00:00
fix(security): gate cp/mv/install into ~/.ssh, credential, and shell-rc files
tools/approval.py already denies tee/redirection writes to every
_SENSITIVE_WRITE_TARGET (~/.ssh/*, ~/.netrc/.pgpass/.npmrc/.pypirc, shell
rc files, ~/.hermes/config.yaml/.env) via the DANGEROUS_PATTERNS tee/`>`
rules, but cp/mv/install were only paired for _SYSTEM_CONFIG_PATH (/etc) and
the project-relative env/config target. So `cp evil ~/.ssh/authorized_keys`
(SSH-key implant / persistence), `cp creds ~/.netrc`, and `cp evil ~/.bashrc`
(login-time command injection) auto-approved while the equivalent tee/`>`
forms were denied — an unpaired write deny is theater (same rationale as
#14639 / commit 4e9d886d, which paired the terminal side for
~/.hermes/config.yaml writes but did not touch these cp/mv/install verbs on
the broader sensitive set).
Add one (cp|mv|install) DANGEROUS_PATTERNS entry reusing the existing
_SENSITIVE_WRITE_TARGET fragment, anchored via _COMMAND_TAIL so it fires on
the destination (last arg) only: reading OUT of a sensitive path
(`cp ~/.ssh/config /tmp/x`) stays auto-approved. Description differs from the
system-config cp entry so the two keep distinct approval keys (no silent
cross-approval). Additive — does not subsume the /etc or project-config rules.
Adds TestSensitiveCopyMovePattern: 5 positive cases (ssh authorized_keys,
ssh private key via mv, netrc via install, bashrc, ~/.hermes/config.yaml) +
2 negative guards (copy FROM ssh, unrelated copy). The ssh/netrc/bashrc
positives fail on main and pass on this branch; the negatives stay green
both ways.
This commit is contained in:
parent
1fa761f8de
commit
da28d5d113
2 changed files with 51 additions and 0 deletions
|
|
@ -633,6 +633,43 @@ class TestProjectSensitiveCopyPattern:
|
|||
assert desc is None
|
||||
|
||||
|
||||
class TestSensitiveCopyMovePattern:
|
||||
"""cp/mv/install OVERWRITING ~/.ssh/*, credential files (~/.netrc etc.),
|
||||
shell rc files, or ~/.hermes/config.yaml/.env must require approval — the
|
||||
tee/redirection forms were already gated (#14639 family / commit 4e9d886d),
|
||||
but cp/mv/install on these targets was an unpaired half-door (key implant /
|
||||
shell-rc command injection slipped through auto-approve)."""
|
||||
|
||||
def test_cp_to_ssh_authorized_keys(self):
|
||||
dangerous, key, desc = detect_dangerous_command("cp /tmp/evil ~/.ssh/authorized_keys")
|
||||
assert dangerous is True
|
||||
assert key is not None
|
||||
|
||||
def test_mv_to_ssh_private_key(self):
|
||||
dangerous, key, desc = detect_dangerous_command("mv /tmp/k ~/.ssh/id_rsa")
|
||||
assert dangerous is True
|
||||
|
||||
def test_install_to_netrc(self):
|
||||
dangerous, key, desc = detect_dangerous_command("install -m600 /tmp/c ~/.netrc")
|
||||
assert dangerous is True
|
||||
|
||||
def test_cp_to_bashrc(self):
|
||||
dangerous, key, desc = detect_dangerous_command("cp /tmp/e ~/.bashrc")
|
||||
assert dangerous is True
|
||||
|
||||
def test_cp_to_hermes_config(self):
|
||||
dangerous, key, desc = detect_dangerous_command("cp /tmp/evil.yaml ~/.hermes/config.yaml")
|
||||
assert dangerous is True
|
||||
|
||||
def test_cp_from_ssh_is_safe(self):
|
||||
dangerous, key, desc = detect_dangerous_command("cp ~/.ssh/config /tmp/x")
|
||||
assert dangerous is False
|
||||
|
||||
def test_cp_unrelated_files_safe(self):
|
||||
dangerous, key, desc = detect_dangerous_command("cp a.txt b.txt")
|
||||
assert dangerous is False
|
||||
|
||||
|
||||
class TestProjectSensitiveTeePattern:
|
||||
def test_tee_to_local_dotenv_requires_approval(self):
|
||||
dangerous, key, desc = detect_dangerous_command("printenv | tee .env.local")
|
||||
|
|
|
|||
|
|
@ -441,6 +441,20 @@ DANGEROUS_PATTERNS = [
|
|||
# /private/etc/ mirror).
|
||||
(rf'\b(cp|mv|install)\b.*\s{_SYSTEM_CONFIG_PATH}', "copy/move file into system config path"),
|
||||
(rf'\b(cp|mv|install)\b.*\s["\']?{_PROJECT_SENSITIVE_WRITE_TARGET}["\']?{_COMMAND_TAIL}', "overwrite project env/config file"),
|
||||
# cp/mv/install OVERWRITING a sensitive credential/SSH/shell-rc/Hermes file.
|
||||
# The tee/redirection patterns above already gate _SENSITIVE_WRITE_TARGET
|
||||
# (~/.ssh/*, ~/.netrc/.pgpass/.npmrc/.pypirc, shell rc files,
|
||||
# ~/.hermes/config.yaml/.env), but cp/mv/install was only paired for /etc and
|
||||
# project-relative env/config — so `cp evil ~/.ssh/authorized_keys` (key
|
||||
# implant), `cp creds ~/.netrc`, and `cp evil ~/.bashrc` (login-time command
|
||||
# injection) slipped through with auto-approve. Same unpaired-door rationale
|
||||
# as #14639 / the sed-tee-redirect pairing on these targets.
|
||||
# Anchor the sensitive target to the command tail so this fires on the
|
||||
# DESTINATION (last arg) only — `cp evil ~/.ssh/authorized_keys` is gated,
|
||||
# but reading OUT of a sensitive path (`cp ~/.ssh/config /tmp/x`) stays safe.
|
||||
# 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"),
|
||||
(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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue