mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
Systematic prune per AGENTS.md test policy, one pass over every major test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli, cron, tui_gateway, honcho/openviking, root-level): - DELETE: source-reading tests (read_text/getsource on prod files), change-detector tests (exact catalog counts, model-name snapshots, config version literals), mock-echo tests (assert a mock returns what it was told), assertion-free/trivial tests, near-duplicate parametrizations (boundaries + one representative kept), async/sync twin duplicates, cosmetic within-file variations. - KEEP (mandatory): security/redaction/approval guards, message-role alternation invariants, prompt-caching/deterministic-call-id invariants, issue-number regression tests (deduped), E2E tests. - 6 test files deleted outright (script-style/no-assert or fully redundant); conftest.py, fakes/, fixtures/ untouched. - tests/acp/conftest.py added: autouse fixture stubs the live models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server tests performed on every session create — test_server.py 147s → 3.4s, and the tests are now genuinely hermetic. - Sleep-based slowness shrunk where safe (codex_ttfb_watchdog, compression_concurrent_fork, etc.); no wall-clock assertion tightened. Verification: full hermetic suite via scripts/run_tests.sh — 2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall (baseline: 583s wall, 13,564s subprocess CPU).
131 lines
4.6 KiB
Python
131 lines
4.6 KiB
Python
"""Tests for agent/file_safety.py read guards — env file blocking.
|
|
|
|
Run with: python -m pytest tests/agent/test_file_safety.py -v
|
|
"""
|
|
|
|
import os
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from agent.file_safety import (
|
|
_BLOCKED_PROJECT_ENV_BASENAMES,
|
|
get_read_block_error,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Project-local .env file blocking (issue #20734)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestEnvFileReadBlocking:
|
|
"""Secret-bearing .env files must be blocked by get_read_block_error."""
|
|
|
|
@pytest.mark.parametrize("basename", [
|
|
".env",
|
|
".env.local",
|
|
".env.development",
|
|
".env.production",
|
|
".env.test",
|
|
".env.staging",
|
|
".envrc",
|
|
])
|
|
def test_blocked_env_basenames(self, basename):
|
|
"""All secret-bearing .env basenames are blocked regardless of directory."""
|
|
path = f"/tmp/project/{basename}"
|
|
error = get_read_block_error(path)
|
|
assert error is not None, f"{basename} should be blocked"
|
|
assert "Access denied" in error
|
|
assert "secret-bearing" in error.lower() or "environment file" in error.lower()
|
|
|
|
|
|
@pytest.mark.parametrize("basename", [
|
|
".ENV",
|
|
".Env.Local",
|
|
".ENV.PRODUCTION",
|
|
".ENVRC",
|
|
])
|
|
def test_blocked_env_basenames_case_insensitive(self, basename):
|
|
"""Secret-bearing .env basenames are blocked regardless of case."""
|
|
error = get_read_block_error(f"/tmp/project/{basename}")
|
|
assert error is not None, f"{basename} should be blocked"
|
|
assert "Access denied" in error
|
|
assert "environment file" in error.lower()
|
|
|
|
|
|
def test_allowed_env_example(self):
|
|
""""The .env.example file is explicitly allowed — it's documentation, not a secret."""
|
|
error = get_read_block_error("/tmp/project/.env.example")
|
|
assert error is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Existing cache-file blocking (regression — must still work)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCacheFileReadBlocking:
|
|
"""Internal Hermes cache files must remain blocked."""
|
|
|
|
def test_hub_index_cache_blocked(self, tmp_path):
|
|
"""Hub index-cache reads are blocked."""
|
|
hermes_home = tmp_path / ".hermes"
|
|
cache = hermes_home / "skills" / ".hub" / "index-cache" / "data.json"
|
|
cache.parent.mkdir(parents=True)
|
|
cache.write_text("{}")
|
|
|
|
with patch("agent.file_safety._hermes_home_path", return_value=hermes_home):
|
|
error = get_read_block_error(str(cache))
|
|
assert error is not None
|
|
assert "internal Hermes cache" in error
|
|
|
|
def test_hub_directory_blocked(self, tmp_path):
|
|
"""Hub directory reads are blocked."""
|
|
hermes_home = tmp_path / ".hermes"
|
|
hub = hermes_home / "skills" / ".hub" / "metadata.json"
|
|
hub.parent.mkdir(parents=True)
|
|
hub.write_text("{}")
|
|
|
|
with patch("agent.file_safety._hermes_home_path", return_value=hermes_home):
|
|
error = get_read_block_error(str(hub))
|
|
assert error is not None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Combined: env guard + cache guard don't interfere
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCombinedGuards:
|
|
"""Both guards should work independently without interference."""
|
|
|
|
def test_env_guard_works_regardless_of_hermes_home(self, tmp_path):
|
|
"""The env basename guard does not depend on HERMES_HOME resolution."""
|
|
hermes_home = tmp_path / ".hermes"
|
|
hermes_home.mkdir()
|
|
|
|
with patch("agent.file_safety._hermes_home_path", return_value=hermes_home):
|
|
# Regular project .env should still be blocked
|
|
error = get_read_block_error("/workspace/.env")
|
|
assert error is not None
|
|
|
|
# .env.example should still be allowed
|
|
error = get_read_block_error("/workspace/.env.example")
|
|
assert error is None
|
|
|
|
def test_cache_guard_still_works_with_env_guard(self, tmp_path):
|
|
"""Cache file blocking still works when env guard is active."""
|
|
hermes_home = tmp_path / ".hermes"
|
|
cache = hermes_home / "skills" / ".hub" / "index-cache" / "x"
|
|
cache.parent.mkdir(parents=True)
|
|
cache.write_text("")
|
|
|
|
with patch("agent.file_safety._hermes_home_path", return_value=hermes_home):
|
|
error = get_read_block_error(str(cache))
|
|
assert error is not None
|
|
assert "internal Hermes cache" in error
|