mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-10 13:31:38 +00:00
A literal "rm -rf /" carried as DATA inside another command's quoted argument — a PR title, a git commit -m message, an echo/printf arg — tripped the unconditional root-filesystem hardline and could not run at all. `gh pr create --title "block rm -rf / spellings"` was blocked outright, because the bare rm path branch matched the mid-string "rm" (via \brm) with the space after "/" satisfying its (\s|$) terminator. Anchor the shared _RM_FLAG_PREFIX to _CMDPOS so the rm hardline rules fire only when rm is an actual command word (start of line, after a separator ; && || |, after a subshell opener $()/backtick, or after sudo/env/exec wrappers) — not when the string appears as an argument value. Broaden the bare-path terminator to also accept shell metacharacters ) ` ; | & so a real wipe inside a command substitution is still caught. The quoted-path branch is unchanged, so quoted root/HOME paths stay blocked. Adds regression tests for both directions: data-arg false positives must NOT block, real wipes at every command position must block.
521 lines
19 KiB
Python
521 lines
19 KiB
Python
"""Tests for the unconditional hardline command blocklist.
|
|
|
|
The hardline list is a floor below yolo: a small set of commands so
|
|
catastrophic they should never run via the agent, regardless of --yolo,
|
|
gateway /yolo, approvals.mode=off, or cron approve mode.
|
|
|
|
Inspired by Mercury Agent's permission-hardened blocklist.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from tools.approval import (
|
|
HARDLINE_PATTERNS,
|
|
check_all_command_guards,
|
|
check_dangerous_command,
|
|
detect_dangerous_command,
|
|
detect_hardline_command,
|
|
disable_session_yolo,
|
|
enable_session_yolo,
|
|
reset_current_session_key,
|
|
set_current_session_key,
|
|
)
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Pattern detection
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Commands that MUST be hardline-blocked.
|
|
_HARDLINE_BLOCK = [
|
|
# rm -rf targeting root / system dirs / home
|
|
"rm -rf /",
|
|
"rm -rf /*",
|
|
"rm -rf /home",
|
|
"rm -rf /home/*",
|
|
"rm -rf /etc",
|
|
"rm -rf /usr",
|
|
"rm -rf /var",
|
|
"rm -rf /boot",
|
|
"rm -rf /bin",
|
|
"rm --recursive --force /",
|
|
"rm -fr /",
|
|
"sudo rm -rf /",
|
|
"rm -rf ~",
|
|
"rm -rf ~/",
|
|
"rm -rf ~/*",
|
|
"rm -rf $HOME",
|
|
# Quoted path idioms — the recommended shell form for paths with special
|
|
# chars. These previously slipped past the floor because the surrounding
|
|
# quote broke both the flag group and the (\s|$) terminator (regression
|
|
# guard: catastrophic disk/home wipe under --yolo / approvals.mode=off).
|
|
'rm -rf "/"',
|
|
"rm -rf '/'",
|
|
'rm -rf "/*"',
|
|
'rm -rf "/etc"',
|
|
"rm -rf '/etc'",
|
|
'rm -rf "/home"',
|
|
'rm -rf "/usr"',
|
|
'rm -rf "$HOME"',
|
|
"rm -rf '$HOME'",
|
|
'rm -rf "$HOME/"',
|
|
'rm -rf "~"',
|
|
'sudo rm -rf "/"',
|
|
'rm -rf "/" && echo done',
|
|
# ${HOME} brace form (universally common, previously unmatched).
|
|
"rm -rf ${HOME}",
|
|
'rm -rf "${HOME}"',
|
|
"rm -fr ${HOME}",
|
|
# Filesystem format
|
|
"mkfs.ext4 /dev/sda1",
|
|
"mkfs /dev/sdb",
|
|
"mkfs.xfs /dev/nvme0n1",
|
|
# Raw block device overwrites
|
|
"dd if=/dev/zero of=/dev/sda bs=1M",
|
|
"dd if=/dev/urandom of=/dev/nvme0n1",
|
|
"dd if=anything of=/dev/hda",
|
|
"echo bad > /dev/sda",
|
|
"cat /dev/urandom > /dev/sdb",
|
|
# Fork bomb
|
|
":(){ :|:& };:",
|
|
# System-wide kill
|
|
"kill -9 -1",
|
|
"kill -1",
|
|
# Shutdown / reboot / halt
|
|
"shutdown -h now",
|
|
"shutdown -r now",
|
|
"sudo shutdown now",
|
|
"reboot",
|
|
"sudo reboot",
|
|
"halt",
|
|
"poweroff",
|
|
"init 0",
|
|
"init 6",
|
|
"telinit 0",
|
|
"systemctl poweroff",
|
|
"systemctl reboot",
|
|
"systemctl halt",
|
|
# Compound / subshell variants
|
|
"ls; reboot",
|
|
"echo done && shutdown -h now",
|
|
"false || halt",
|
|
"$(reboot)",
|
|
"`shutdown now`",
|
|
"sudo -E shutdown now",
|
|
"env FOO=1 reboot",
|
|
"exec shutdown",
|
|
"nohup reboot",
|
|
"setsid poweroff",
|
|
]
|
|
|
|
|
|
# Commands that look superficially similar but must NOT be hardline-blocked.
|
|
_HARDLINE_ALLOW = [
|
|
# rm on non-protected paths
|
|
"rm -rf /tmp/foo",
|
|
"rm -rf /tmp/*",
|
|
"rm -rf ./build",
|
|
"rm -rf node_modules",
|
|
"rm -rf /home/user/scratch", # subpath of /home, not /home itself
|
|
"rm -rf ~/Downloads/old",
|
|
"rm -rf $HOME/tmp",
|
|
"rm foo.txt",
|
|
"rm -rf some/path",
|
|
# A dangerous-looking command embedded as a quoted *argument* to another
|
|
# command must not trip the floor: the path is immediately followed by a
|
|
# closing quote with no matching opening quote of its own, so the
|
|
# quote-tolerant matcher must still ignore it (no new false positives).
|
|
'git commit -m "rm -rf /"',
|
|
'git commit -m "wipe with rm -rf /etc"',
|
|
# dd to regular files
|
|
"dd if=/dev/zero of=./image.bin",
|
|
"dd if=./data of=./backup.bin",
|
|
# Redirect to regular files / non-block devices
|
|
"echo done > /tmp/flag",
|
|
"echo test > /dev/null",
|
|
# Reading devices is fine
|
|
"ls /dev/sda",
|
|
"cat /dev/urandom | head -c 10",
|
|
# Unrelated commands that happen to contain the trigger word
|
|
"grep 'shutdown' logs.txt",
|
|
"echo reboot",
|
|
"echo '# init 0 in comment'",
|
|
"cat rebooting.log",
|
|
"echo 'halt and catch fire'",
|
|
"python3 -c 'print(\"shutdown\")'",
|
|
"find . -name '*reboot*'",
|
|
# Word-boundary protection
|
|
"mkfs_helper --version",
|
|
# systemctl non-destructive verbs
|
|
"systemctl status nginx",
|
|
"systemctl restart nginx",
|
|
"systemctl stop nginx",
|
|
"systemctl start nginx",
|
|
# targeted kill
|
|
"kill -9 12345",
|
|
"kill -HUP 1234",
|
|
"pkill python",
|
|
# Ordinary ops
|
|
"git status",
|
|
"npm run build",
|
|
"sudo apt update",
|
|
"curl https://example.com | head",
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("command", _HARDLINE_BLOCK)
|
|
def test_hardline_detection_blocks(command):
|
|
is_hl, desc = detect_hardline_command(command)
|
|
assert is_hl, f"expected hardline to match {command!r}"
|
|
assert desc, "hardline match must provide a description"
|
|
|
|
|
|
@pytest.mark.parametrize("command", _HARDLINE_ALLOW)
|
|
def test_hardline_detection_allows(command):
|
|
is_hl, desc = detect_hardline_command(command)
|
|
assert not is_hl, f"expected hardline NOT to match {command!r} (got: {desc})"
|
|
assert desc is None
|
|
|
|
|
|
# Commands written with the ordinary quoting / brace shell idioms that
|
|
# previously slipped past the floor. Kept as an explicit regression set so
|
|
# the intent (quoting `rm -rf "/"` must not be a disk-wipe bypass) survives
|
|
# any future refactor of the rm patterns.
|
|
_QUOTED_BRACE_BYPASS = [
|
|
'rm -rf "/"',
|
|
"rm -rf '/'",
|
|
'rm -rf "/etc"',
|
|
'rm -rf "/home"',
|
|
'rm -rf "$HOME"',
|
|
"rm -rf ${HOME}",
|
|
'rm -rf "${HOME}"',
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("command", _QUOTED_BRACE_BYPASS)
|
|
def test_quoted_and_brace_paths_are_hardline_blocked(command):
|
|
"""Quoted paths and ${HOME} must hit the floor (was a silent bypass)."""
|
|
is_hl, desc = detect_hardline_command(command)
|
|
assert is_hl, f"quoting/brace bypass leaked through hardline floor: {command!r}"
|
|
assert desc
|
|
|
|
|
|
# Commands that carry the literal string "rm -rf /" (or a sibling) as DATA in
|
|
# another command's quoted argument — a PR title, a commit message, an echo /
|
|
# printf argument. The shell never executes that text as an rm command, so the
|
|
# hardline floor must NOT fire; otherwise the command cannot run at all (this
|
|
# blocked `gh pr create --title "…rm -rf /…"` outright). Regression guard for
|
|
# the command-position anchor on the rm rules.
|
|
_DATA_ARG_NOT_A_COMMAND = [
|
|
'gh pr create --title "block rm -rf / spellings"',
|
|
'git commit -m "fixes rm -rf / bypass"',
|
|
'echo "run rm -rf / now"',
|
|
'echo "rm -rf /"',
|
|
'printf "%s" "rm -rf /"',
|
|
'gh issue comment 1 --body "the fix blocks rm -rf //"',
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("command", _DATA_ARG_NOT_A_COMMAND)
|
|
def test_root_wipe_string_as_data_arg_is_not_hardline(command):
|
|
""""rm -rf /" as a quoted argument to another command is data, not a wipe."""
|
|
is_hl, desc = detect_hardline_command(command)
|
|
assert not is_hl, f"false positive: quoted data arg hit hardline floor: {command!r} ({desc})"
|
|
|
|
|
|
# Real root wipes at every command position — bare, chained after a separator,
|
|
# inside a command substitution ($()/backtick), or after sudo/env wrappers.
|
|
# The command-position anchor must keep catching all of these; the substitution
|
|
# forms exercise the shell-metacharacter terminator on the bare path branch.
|
|
_COMMAND_POSITION_ROOT_WIPES = [
|
|
"rm -rf /",
|
|
"ls && rm -rf /",
|
|
"ls; rm -rf /",
|
|
"echo x | rm -rf /",
|
|
"sudo rm -rf /",
|
|
"env X=1 rm -rf /",
|
|
"$(rm -rf /)",
|
|
"`rm -rf /`",
|
|
'echo "$(rm -rf /)"',
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("command", _COMMAND_POSITION_ROOT_WIPES)
|
|
def test_root_wipe_at_command_position_is_hardline(command):
|
|
"""A real `rm -rf /` at any command position stays hardline-blocked."""
|
|
is_hl, desc = detect_hardline_command(command)
|
|
assert is_hl, f"real root wipe leaked past the floor: {command!r}"
|
|
assert desc
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Shell line-continuation bypass
|
|
# -------------------------------------------------------------------------
|
|
#
|
|
# A backslash immediately followed by a newline is a POSIX line
|
|
# continuation: the shell removes BOTH characters and joins the tokens, so
|
|
# `rm -rf \<newline>/` executes as `rm -rf /`. The normalizer used to strip
|
|
# only backslash-escapes of NON-newline characters (`\\([^\n])`), leaving the
|
|
# dangling backslash wedged between tokens — which broke the structured
|
|
# rm/dd/mkfs patterns and let a root wipe slip past the hardline floor.
|
|
|
|
# (command_with_continuation, description_substring) — each is the
|
|
# line-continuation form of a command already in _HARDLINE_BLOCK.
|
|
_HARDLINE_LINE_CONTINUATION = [
|
|
("rm -rf \\\n/", "root"), # split before the path
|
|
("rm -r\\\nf /", "root"), # split inside the flag bundle
|
|
("rm -rf \\\n~", "home"), # home-directory wipe
|
|
("rm -rf \\\r\n/", "root"), # CRLF line ending
|
|
("mkfs.ext4 \\\n/dev/sda1", "mkfs"), # filesystem format
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("command,desc_substr", _HARDLINE_LINE_CONTINUATION)
|
|
def test_hardline_blocks_line_continuation(command, desc_substr):
|
|
is_hl, desc = detect_hardline_command(command)
|
|
assert is_hl, f"line-continuation bypassed hardline detection: {command!r}"
|
|
assert desc and desc_substr in desc.lower(), (
|
|
f"unexpected description {desc!r} for {command!r}"
|
|
)
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Integration with the approval flow
|
|
# -------------------------------------------------------------------------
|
|
|
|
@pytest.fixture
|
|
def clean_session(monkeypatch):
|
|
"""Reset session-scoped approval state around each test."""
|
|
monkeypatch.delenv("HERMES_YOLO_MODE", raising=False)
|
|
monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
|
|
monkeypatch.delenv("HERMES_GATEWAY_SESSION", raising=False)
|
|
monkeypatch.delenv("HERMES_CRON_SESSION", raising=False)
|
|
monkeypatch.delenv("HERMES_EXEC_ASK", raising=False)
|
|
token = set_current_session_key("hardline_test")
|
|
try:
|
|
disable_session_yolo("hardline_test")
|
|
yield
|
|
finally:
|
|
disable_session_yolo("hardline_test")
|
|
reset_current_session_key(token)
|
|
|
|
|
|
def test_check_dangerous_command_blocks_hardline(clean_session):
|
|
result = check_dangerous_command("rm -rf /", "local")
|
|
assert result["approved"] is False
|
|
assert result.get("hardline") is True
|
|
assert "BLOCKED (hardline)" in result["message"]
|
|
|
|
|
|
def test_check_all_command_guards_blocks_hardline(clean_session):
|
|
result = check_all_command_guards("rm -rf /", "local")
|
|
assert result["approved"] is False
|
|
assert result.get("hardline") is True
|
|
assert "BLOCKED (hardline)" in result["message"]
|
|
|
|
|
|
def test_yolo_env_var_cannot_bypass_hardline(clean_session, monkeypatch):
|
|
"""HERMES_YOLO_MODE=1 must not bypass the hardline floor."""
|
|
monkeypatch.setenv("HERMES_YOLO_MODE", "1")
|
|
|
|
for cmd in ['rm -rf /', 'rm -rf "/"', 'rm -rf "$HOME"', "rm -rf ${HOME}",
|
|
"shutdown -h now", "mkfs.ext4 /dev/sda", "reboot"]:
|
|
r1 = check_dangerous_command(cmd, "local")
|
|
assert r1["approved"] is False, f"yolo leaked hardline on {cmd!r} (check_dangerous_command)"
|
|
assert r1.get("hardline") is True
|
|
|
|
r2 = check_all_command_guards(cmd, "local")
|
|
assert r2["approved"] is False, f"yolo leaked hardline on {cmd!r} (check_all_command_guards)"
|
|
assert r2.get("hardline") is True
|
|
|
|
|
|
def test_line_continuation_root_wipe_cannot_bypass_hardline(clean_session, monkeypatch):
|
|
"""A line-continuation root wipe must stay blocked even under yolo.
|
|
|
|
`rm -rf \\<newline>/` runs as `rm -rf /`. Yolo bypasses the regular
|
|
dangerous-command layer, so the hardline floor is the only thing left to
|
|
catch it — it must hold.
|
|
"""
|
|
monkeypatch.setenv("HERMES_YOLO_MODE", "1")
|
|
|
|
result = check_all_command_guards("rm -rf \\\n/", "local")
|
|
assert result["approved"] is False, "yolo leaked a line-continuation root wipe"
|
|
assert result.get("hardline") is True
|
|
assert "BLOCKED (hardline)" in result["message"]
|
|
|
|
|
|
def test_session_yolo_cannot_bypass_hardline(clean_session):
|
|
"""Gateway /yolo (session-scoped) must not bypass the hardline floor."""
|
|
enable_session_yolo("hardline_test")
|
|
|
|
result = check_dangerous_command("rm -rf /", "local")
|
|
assert result["approved"] is False
|
|
assert result.get("hardline") is True
|
|
|
|
result = check_all_command_guards("rm -rf /", "local")
|
|
assert result["approved"] is False
|
|
assert result.get("hardline") is True
|
|
|
|
|
|
def test_approvals_mode_off_cannot_bypass_hardline(clean_session, monkeypatch, tmp_path):
|
|
"""config approvals.mode=off (yolo-equivalent) must not bypass hardline."""
|
|
# _get_approval_mode() reads from hermes config; simplest path: monkeypatch the helper.
|
|
import tools.approval as approval_mod
|
|
monkeypatch.setattr(approval_mod, "_get_approval_mode", lambda: "off")
|
|
|
|
result = check_all_command_guards("rm -rf /", "local")
|
|
assert result["approved"] is False
|
|
assert result.get("hardline") is True
|
|
|
|
|
|
def test_cron_approve_mode_cannot_bypass_hardline(clean_session, monkeypatch):
|
|
"""Cron sessions with cron_mode=approve must not bypass hardline."""
|
|
monkeypatch.setenv("HERMES_CRON_SESSION", "1")
|
|
import tools.approval as approval_mod
|
|
monkeypatch.setattr(approval_mod, "_get_cron_approval_mode", lambda: "approve")
|
|
|
|
result = check_all_command_guards("rm -rf /", "local")
|
|
assert result["approved"] is False
|
|
assert result.get("hardline") is True
|
|
|
|
|
|
def test_container_backends_still_bypass(clean_session):
|
|
"""Containerized backends remain bypass-approved — they can't touch the host.
|
|
|
|
Hardline only protects environments with real host impact (local, ssh).
|
|
"""
|
|
for env in ("docker", "singularity", "modal", "daytona"):
|
|
r1 = check_dangerous_command("rm -rf /", env)
|
|
assert r1["approved"] is True, f"container {env} should still bypass"
|
|
r2 = check_all_command_guards("rm -rf /", env)
|
|
assert r2["approved"] is True, f"container {env} should still bypass"
|
|
|
|
|
|
def test_hardline_runs_before_dangerous_detection(clean_session):
|
|
"""Hardline command should return hardline block, not dangerous approval prompt."""
|
|
# `rm -rf /` is both hardline AND matches DANGEROUS_PATTERNS. Hardline must win.
|
|
is_dangerous, _, _ = detect_dangerous_command("rm -rf /")
|
|
assert is_dangerous, "precondition: rm -rf / is also in DANGEROUS_PATTERNS"
|
|
|
|
result = check_dangerous_command("rm -rf /", "local")
|
|
assert result.get("hardline") is True
|
|
|
|
|
|
def test_recoverable_dangerous_commands_still_pass_yolo(clean_session, monkeypatch):
|
|
"""Yolo still bypasses the regular DANGEROUS_PATTERNS list.
|
|
|
|
This confirms we haven't broken the yolo escape hatch — only narrowed it.
|
|
"""
|
|
monkeypatch.setenv("HERMES_YOLO_MODE", "1")
|
|
|
|
# These are dangerous but NOT hardline — yolo should still pass them.
|
|
for cmd in ["rm -rf /tmp/x", "chmod -R 777 .", "git reset --hard", "git push --force"]:
|
|
# Sanity: still flagged as dangerous
|
|
is_dangerous, _, _ = detect_dangerous_command(cmd)
|
|
assert is_dangerous, f"precondition: {cmd!r} should be in DANGEROUS_PATTERNS"
|
|
# But NOT hardline
|
|
is_hl, _ = detect_hardline_command(cmd)
|
|
assert not is_hl, f"{cmd!r} should not be hardline"
|
|
# And yolo bypasses the dangerous check
|
|
result = check_dangerous_command(cmd, "local")
|
|
assert result["approved"] is True, f"yolo should have bypassed {cmd!r}"
|
|
|
|
|
|
def test_hardline_list_is_small():
|
|
"""Hardline list stays focused on unrecoverable commands only.
|
|
|
|
If you're adding a 20th+ pattern, reconsider — it probably belongs in
|
|
DANGEROUS_PATTERNS where yolo can still bypass it.
|
|
"""
|
|
assert len(HARDLINE_PATTERNS) <= 20, (
|
|
f"HARDLINE_PATTERNS has grown to {len(HARDLINE_PATTERNS)} entries; "
|
|
"only truly unrecoverable commands belong here."
|
|
)
|
|
|
|
|
|
# =========================================================================
|
|
# Sudo stdin guard — blocks "sudo -S" without SUDO_PASSWORD
|
|
# =========================================================================
|
|
|
|
_SUDO_STDIN_BLOCK = [
|
|
"sudo -S whoami",
|
|
"echo hunter2 | sudo -S whoami",
|
|
"sudo -S -u root whoami",
|
|
"sudo -S apt-get install foo",
|
|
"echo password | sudo -S systemctl restart nginx",
|
|
"sudo -k && sudo -S whoami",
|
|
]
|
|
|
|
_SUDO_STDIN_ALLOW = [
|
|
# Plain sudo without -S — goes through normal approval
|
|
"sudo whoami",
|
|
"sudo apt-get update",
|
|
"sudo -u root whoami",
|
|
# -S flag not attached to sudo
|
|
"echo -S hello",
|
|
"some_tool -S thing",
|
|
# Literal text mention of sudo
|
|
"echo 'use sudo -S to pipe passwords'",
|
|
]
|
|
|
|
_SUDO_STDIN_BLOCK_YOLO = [
|
|
"sudo -S whoami",
|
|
"echo hunter2 | sudo -S apt-get install",
|
|
]
|
|
|
|
|
|
def test_sudo_stdin_guard_detects_without_password():
|
|
"""sudo -S is dangerous when SUDO_PASSWORD is not configured."""
|
|
import tools.approval as approval_mod
|
|
|
|
for cmd in _SUDO_STDIN_BLOCK:
|
|
is_blocked, desc = approval_mod._check_sudo_stdin_guard(cmd)
|
|
assert is_blocked, f"expected sudo stdin guard to block {cmd!r}"
|
|
assert "sudo" in desc.lower()
|
|
|
|
|
|
def test_sudo_stdin_guard_allows_benign_commands():
|
|
"""Commands without explicit sudo -S are not blocked."""
|
|
import tools.approval as approval_mod
|
|
|
|
for cmd in _SUDO_STDIN_ALLOW:
|
|
is_blocked, desc = approval_mod._check_sudo_stdin_guard(cmd)
|
|
assert not is_blocked, f"expected sudo stdin guard NOT to block {cmd!r}"
|
|
|
|
|
|
def test_sudo_stdin_guard_bypassed_when_password_configured(monkeypatch):
|
|
"""When SUDO_PASSWORD is set, sudo -S is legitimate (injected by transform)."""
|
|
import tools.approval as approval_mod
|
|
|
|
monkeypatch.setenv("SUDO_PASSWORD", "testpass")
|
|
for cmd in _SUDO_STDIN_BLOCK:
|
|
is_blocked, _ = approval_mod._check_sudo_stdin_guard(cmd)
|
|
assert not is_blocked, f"with SUDO_PASSWORD set, {cmd!r} should NOT be blocked"
|
|
|
|
|
|
def test_sudo_stdin_guard_blocks_via_check_all_command_guards(clean_session):
|
|
"""Integration: check_all_command_guards returns block for sudo -S."""
|
|
for cmd in _SUDO_STDIN_BLOCK:
|
|
result = check_all_command_guards(cmd, "local")
|
|
assert result["approved"] is False, f"expected block on {cmd!r}"
|
|
# Should NOT be marked as hardline (it's sudo-specific)
|
|
assert result.get("hardline") is not True
|
|
assert "BLOCKED" in result["message"]
|
|
assert "sudo -S" in result["message"].lower() or "sudo password" in result["message"].lower()
|
|
|
|
|
|
def test_sudo_stdin_guard_not_blocked_by_yolo(clean_session, monkeypatch):
|
|
"""yolo/approvals.mode=off must NOT bypass sudo stdin guard."""
|
|
monkeypatch.setenv("HERMES_YOLO_MODE", "1")
|
|
|
|
for cmd in _SUDO_STDIN_BLOCK_YOLO:
|
|
result = check_all_command_guards(cmd, "local")
|
|
assert result["approved"] is False, f"yolo leaked sudo guard on {cmd!r}"
|
|
|
|
|
|
def test_sudo_stdin_guard_container_bypass(clean_session):
|
|
"""Containerized backends still bypass — they can't touch the host."""
|
|
for env in ("docker", "singularity", "modal", "daytona"):
|
|
for cmd in _SUDO_STDIN_BLOCK:
|
|
result = check_all_command_guards(cmd, env)
|
|
assert result["approved"] is True, f"container {env} should bypass sudo guard on {cmd!r}"
|