mirror of
https://github.com/rookiestar28/ComfyUI-OpenClaw.git
synced 2026-08-14 08:52:45 +00:00
155 lines
6.0 KiB
Python
155 lines
6.0 KiB
Python
import os
|
|
import sys
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
# We need to test the logic in security_gate.py
|
|
# Use a mock for sys.argv and services.access_control
|
|
|
|
|
|
class TestStartupGateLogic(unittest.TestCase):
|
|
|
|
def test_check_network_exposure_loopback(self):
|
|
"""Verify internal logic for loopback detection."""
|
|
from services.security_gate import SecurityGate
|
|
|
|
# Test case: No --listen args
|
|
with patch.object(sys, "argv", ["main.py"]):
|
|
is_exposed = SecurityGate._check_network_exposure()
|
|
self.assertFalse(is_exposed)
|
|
|
|
def test_check_network_exposure_listen(self):
|
|
"""Verify detection of --listen."""
|
|
from services.security_gate import SecurityGate
|
|
|
|
for arg in [["main.py", "--listen"], ["main.py", "--listen", "0.0.0.0"]]:
|
|
with patch.object(sys, "argv", arg):
|
|
is_exposed = SecurityGate._check_network_exposure()
|
|
self.assertTrue(is_exposed)
|
|
|
|
def test_enforcement_exposed_no_auth(self):
|
|
"""Exposed + No Auth = Fail."""
|
|
from services.security_gate import SecurityGate
|
|
|
|
# Patch the SOURCE of the import, because security_gate imports it inside the function
|
|
with (
|
|
patch.object(sys, "argv", ["main.py", "--listen"]),
|
|
patch(
|
|
"services.access_control.is_any_token_configured", return_value=False
|
|
),
|
|
):
|
|
|
|
# Mock other dependencies to isolate the S45 check
|
|
# We need to ensure we don't accidentally fail on other checks
|
|
mock_config = MagicMock()
|
|
mock_config.allow_any_public_llm_host = False
|
|
mock_config.allow_insecure_base_url = False
|
|
mock_config.webhook_auth_mode = "secret"
|
|
mock_config.security_dangerous_bind_override = False
|
|
|
|
with (
|
|
patch("services.modules.is_module_enabled", return_value=False),
|
|
patch("services.runtime_config.get_config", return_value=mock_config),
|
|
patch("services.security_gate.callable", return_value=True),
|
|
patch("services.runtime_profile.is_hardened_mode", return_value=False),
|
|
):
|
|
|
|
passed, warnings, fatal_errors = (
|
|
SecurityGate.verify_mandatory_controls()
|
|
)
|
|
|
|
# Expect failure
|
|
self.assertFalse(passed)
|
|
self.assertTrue(
|
|
any("CRITICAL SECURITY RISK" in i for i in fatal_errors)
|
|
)
|
|
|
|
def test_enforcement_exposed_no_auth_override(self):
|
|
"""Exposed + No Auth + Override = PASS (with Warning)."""
|
|
from services.security_gate import SecurityGate
|
|
|
|
with (
|
|
patch.object(sys, "argv", ["main.py", "--listen"]),
|
|
patch(
|
|
"services.access_control.is_any_token_configured", return_value=False
|
|
),
|
|
):
|
|
|
|
mock_config = MagicMock()
|
|
mock_config.allow_any_public_llm_host = False
|
|
mock_config.allow_insecure_base_url = False
|
|
mock_config.webhook_auth_mode = "secret"
|
|
# ENABLE OVERRIDE
|
|
mock_config.security_dangerous_bind_override = True
|
|
|
|
with (
|
|
patch("services.modules.is_module_enabled", return_value=False),
|
|
patch("services.runtime_config.get_config", return_value=mock_config),
|
|
patch("services.security_gate.callable", return_value=True),
|
|
patch("services.runtime_profile.is_hardened_mode", return_value=False),
|
|
):
|
|
|
|
passed, warnings, fatal_errors = (
|
|
SecurityGate.verify_mandatory_controls()
|
|
)
|
|
|
|
self.assertTrue(
|
|
passed,
|
|
"Override should prevent FATAL errors, so it should PASS verification phase",
|
|
)
|
|
self.assertTrue(
|
|
any("WARNING: Server is exposed" in i for i in warnings)
|
|
)
|
|
self.assertFalse(fatal_errors, "Should not report Critical Risk")
|
|
|
|
def test_enforcement_loopback_no_auth(self):
|
|
"""Loopback + No Auth = Pass (S45 Update)."""
|
|
from services.security_gate import SecurityGate
|
|
|
|
with (
|
|
patch.object(sys, "argv", ["main.py"]),
|
|
patch(
|
|
"services.access_control.is_any_token_configured", return_value=False
|
|
),
|
|
):
|
|
|
|
mock_config = MagicMock()
|
|
mock_config.allow_any_public_llm_host = False
|
|
mock_config.allow_insecure_base_url = False
|
|
mock_config.webhook_auth_mode = "secret" # Satisfy webhook check if enabled
|
|
|
|
with (
|
|
patch("services.modules.is_module_enabled", return_value=False),
|
|
patch("services.runtime_config.get_config", return_value=mock_config),
|
|
patch("services.security_gate.callable", return_value=True),
|
|
patch("services.runtime_profile.is_hardened_mode", return_value=False),
|
|
):
|
|
|
|
passed, warnings, fatal = SecurityGate.verify_mandatory_controls()
|
|
|
|
# Expect PASS
|
|
self.assertTrue(
|
|
passed,
|
|
f"Should pass in loopback mode even without auth. Issues: {fatal}",
|
|
)
|
|
|
|
def test_enforcement_gate_crash(self):
|
|
"""Test that FATAL errors actually crash the app in Minimal mode."""
|
|
from services.security_gate import SecurityGate, enforce_startup_gate
|
|
|
|
# Simulate a FATAL condition (Exposed + No Auth)
|
|
# We Mock verify_mandatory_controls to return Fatal error
|
|
with patch.object(
|
|
SecurityGate,
|
|
"verify_mandatory_controls",
|
|
return_value=(False, [], ["FATAL ERROR"]),
|
|
):
|
|
with patch("services.runtime_profile.is_hardened_mode", return_value=False):
|
|
with self.assertRaises(RuntimeError) as cm:
|
|
enforce_startup_gate()
|
|
self.assertIn("FATAL ERROR", str(cm.exception))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|