hermes-agent/tests/tools/test_write_deny.py
Teknium 18f3fc8a6f
fix(tests): resolve 17 persistent CI test failures (#15084)
Make the main-branch test suite pass again. Most failures were tests
still asserting old shapes after recent refactors; two were real source
bugs.

Source fixes:
- tools/mcp_tool.py: _kill_orphaned_mcp_children() slept 2s on every
  shutdown even when no tracked PIDs existed, making test_shutdown_is_parallel
  measure ~3s for 3 parallel 1s shutdowns. Early-return when pids is empty.
- hermes_cli/tips.py: tip 105 was 157 chars; corpus max is 150.

Test fixes (mostly stale mock targets / missing fixture fields):
- test_zombie_process_cleanup, test_agent_cache: patch run_agent.cleanup_vm
  (the local name bound at import), not tools.terminal_tool.cleanup_vm.
- test_browser_camofox: patch tools.browser_camofox.load_config, not
  hermes_cli.config.load_config (the source module, not the resolved one).
- test_flush_memories_codex._chat_response_with_memory_call: add
  finish_reason, tool_call.id, tool_call.type so the chat_completions
  transport normalizer doesn't AttributeError.
- test_concurrent_interrupt: polling_tool signature now accepts
  messages= kwarg that _invoke_tool() passes through.
- test_minimax_provider: add _fallback_chain=[] to the __new__'d agent
  so switch_model() doesn't AttributeError.
- test_skills_config: SKILLS_DIR MagicMock + .rglob stopped working
  after the scanner switched to agent.skill_utils.iter_skill_index_files
  (os.walk-based). Point SKILLS_DIR at a real tmp_path and patch
  agent.skill_utils.get_external_skills_dirs.
- test_browser_cdp_tool: browser_cdp toolset was intentionally split into
  'browser-cdp' (commit 96b0f3700) so its stricter check_fn doesn't gate
  the whole browser toolset; test now expects 'browser-cdp'.
- test_registry: add tools.browser_dialog_tool to the expected
  builtin-discovery set (PR #14540 added it).
- test_file_tools TestPatchHints: patch_tool surfaces hints as a '_hint'
  key on the JSON payload, not inline '[Hint: ...' text.
- test_write_deny test_hermes_env: resolve .env via get_hermes_home() so
  the path matches the profile-aware denylist under hermetic HERMES_HOME.
- test_checkpoint_manager test_falls_back_to_parent: guard the walk-up
  so a stray /tmp/pyproject.toml on the host doesn't pick up /tmp as the
  project root.
- test_quick_commands: set cli.session_id in the __new__'d CLI so the
  alias-args path doesn't trip AttributeError when fuzzy-matching leaks
  a skill command across xdist test distribution.
2026-04-24 03:46:46 -07:00

88 lines
3.1 KiB
Python

"""Tests for _is_write_denied() — verifies deny list blocks sensitive paths on all platforms."""
import os
import pytest
from pathlib import Path
from tools.file_operations import _is_write_denied
class TestWriteDenyExactPaths:
def test_etc_shadow(self):
assert _is_write_denied("/etc/shadow") is True
def test_etc_passwd(self):
assert _is_write_denied("/etc/passwd") is True
def test_etc_sudoers(self):
assert _is_write_denied("/etc/sudoers") is True
def test_ssh_authorized_keys(self):
assert _is_write_denied("~/.ssh/authorized_keys") is True
def test_ssh_id_rsa(self):
path = os.path.join(str(Path.home()), ".ssh", "id_rsa")
assert _is_write_denied(path) is True
def test_ssh_id_ed25519(self):
path = os.path.join(str(Path.home()), ".ssh", "id_ed25519")
assert _is_write_denied(path) is True
def test_netrc(self):
path = os.path.join(str(Path.home()), ".netrc")
assert _is_write_denied(path) is True
def test_hermes_env(self):
# ``.env`` under the active HERMES_HOME (profile-aware, not just
# ``~/.hermes``) must be write-denied. The hermetic test conftest
# points HERMES_HOME at a tempdir — resolve via get_hermes_home()
# to match the denylist.
from hermes_constants import get_hermes_home
path = str(get_hermes_home() / ".env")
assert _is_write_denied(path) is True
def test_shell_profiles(self):
home = str(Path.home())
for name in [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile"]:
assert _is_write_denied(os.path.join(home, name)) is True, f"{name} should be denied"
def test_package_manager_configs(self):
home = str(Path.home())
for name in [".npmrc", ".pypirc", ".pgpass"]:
assert _is_write_denied(os.path.join(home, name)) is True, f"{name} should be denied"
class TestWriteDenyPrefixes:
def test_ssh_prefix(self):
path = os.path.join(str(Path.home()), ".ssh", "some_key")
assert _is_write_denied(path) is True
def test_aws_prefix(self):
path = os.path.join(str(Path.home()), ".aws", "credentials")
assert _is_write_denied(path) is True
def test_gnupg_prefix(self):
path = os.path.join(str(Path.home()), ".gnupg", "secring.gpg")
assert _is_write_denied(path) is True
def test_kube_prefix(self):
path = os.path.join(str(Path.home()), ".kube", "config")
assert _is_write_denied(path) is True
def test_sudoers_d_prefix(self):
assert _is_write_denied("/etc/sudoers.d/custom") is True
def test_systemd_prefix(self):
assert _is_write_denied("/etc/systemd/system/evil.service") is True
class TestWriteAllowed:
def test_tmp_file(self):
assert _is_write_denied("/tmp/safe_file.txt") is False
def test_project_file(self):
assert _is_write_denied("/home/user/project/main.py") is False
def test_hermes_config_not_env(self):
path = os.path.join(str(Path.home()), ".hermes", "config.yaml")
assert _is_write_denied(path) is False