test_weak_credential_guard.py
1 """Tests for gateway weak credential rejection at startup. 2 3 Ported from openclaw/openclaw#64586: rejects known-weak placeholder 4 tokens at gateway startup instead of letting them silently fail 5 against platform APIs. 6 """ 7 8 import logging 9 10 import pytest 11 12 from gateway.config import PlatformConfig, Platform, _validate_gateway_config 13 14 15 # --------------------------------------------------------------------------- 16 # Helper: create a minimal GatewayConfig with one enabled platform 17 # --------------------------------------------------------------------------- 18 19 20 def _make_gateway_config(platform, token, enabled=True, **extra_kwargs): 21 """Create a minimal GatewayConfig-like object for validation testing.""" 22 from gateway.config import GatewayConfig 23 24 config = GatewayConfig(platforms={}) 25 pconfig = PlatformConfig(enabled=enabled, token=token, **extra_kwargs) 26 config.platforms[platform] = pconfig 27 return config 28 29 30 def _validate_and_return(config): 31 """Call _validate_gateway_config and return the config (mutated in place).""" 32 _validate_gateway_config(config) 33 return config 34 35 36 # --------------------------------------------------------------------------- 37 # Unit tests: platform token placeholder rejection 38 # --------------------------------------------------------------------------- 39 40 41 class TestPlatformTokenPlaceholderGuard: 42 """Verify that _validate_gateway_config disables platforms with placeholder tokens.""" 43 44 def test_rejects_triple_asterisk(self, caplog): 45 """'***' is the .env.example placeholder — should be rejected.""" 46 config = _make_gateway_config(Platform.TELEGRAM, "***") 47 with caplog.at_level(logging.ERROR): 48 _validate_and_return(config) 49 assert config.platforms[Platform.TELEGRAM].enabled is False 50 assert "placeholder" in caplog.text.lower() 51 52 def test_rejects_changeme(self, caplog): 53 config = _make_gateway_config(Platform.DISCORD, "changeme") 54 with caplog.at_level(logging.ERROR): 55 _validate_and_return(config) 56 assert config.platforms[Platform.DISCORD].enabled is False 57 58 def test_rejects_your_api_key(self, caplog): 59 config = _make_gateway_config(Platform.SLACK, "your_api_key") 60 with caplog.at_level(logging.ERROR): 61 _validate_and_return(config) 62 assert config.platforms[Platform.SLACK].enabled is False 63 64 def test_rejects_placeholder(self, caplog): 65 config = _make_gateway_config(Platform.MATRIX, "placeholder") 66 with caplog.at_level(logging.ERROR): 67 _validate_and_return(config) 68 assert config.platforms[Platform.MATRIX].enabled is False 69 70 def test_accepts_real_token(self, caplog): 71 """A real-looking bot token should pass validation.""" 72 config = _make_gateway_config( 73 Platform.TELEGRAM, "7123456789:AAHdqTcvCH1vGWJxfSeOfSAs0K5PALDsaw" 74 ) 75 with caplog.at_level(logging.ERROR): 76 _validate_and_return(config) 77 assert config.platforms[Platform.TELEGRAM].enabled is True 78 assert "placeholder" not in caplog.text.lower() 79 80 def test_accepts_empty_token_without_error(self, caplog): 81 """Empty tokens get a warning (existing behavior), not a placeholder error.""" 82 config = _make_gateway_config(Platform.TELEGRAM, "") 83 with caplog.at_level(logging.WARNING): 84 _validate_and_return(config) 85 # Empty token doesn't trigger placeholder rejection — enabled stays True 86 # (the existing empty-token warning is separate) 87 assert config.platforms[Platform.TELEGRAM].enabled is True 88 89 def test_disabled_platform_not_checked(self, caplog): 90 """Disabled platforms should not be validated.""" 91 config = _make_gateway_config(Platform.TELEGRAM, "***", enabled=False) 92 with caplog.at_level(logging.ERROR): 93 _validate_and_return(config) 94 assert "placeholder" not in caplog.text.lower() 95 96 def test_rejects_whitespace_padded_placeholder(self, caplog): 97 """Whitespace-padded placeholders should still be caught.""" 98 config = _make_gateway_config(Platform.TELEGRAM, " *** ") 99 with caplog.at_level(logging.ERROR): 100 _validate_and_return(config) 101 assert config.platforms[Platform.TELEGRAM].enabled is False 102 103 104 # --------------------------------------------------------------------------- 105 # Integration test: API server placeholder key on network-accessible host 106 # --------------------------------------------------------------------------- 107 108 109 class TestAPIServerPlaceholderKeyGuard: 110 """Verify that the API server rejects placeholder keys on network hosts.""" 111 112 @pytest.mark.asyncio 113 async def test_refuses_wildcard_with_placeholder_key(self): 114 from gateway.platforms.api_server import APIServerAdapter 115 116 adapter = APIServerAdapter( 117 PlatformConfig(enabled=True, extra={"host": "0.0.0.0", "key": "changeme"}) 118 ) 119 result = await adapter.connect() 120 assert result is False 121 122 @pytest.mark.asyncio 123 async def test_refuses_wildcard_with_asterisk_key(self): 124 from gateway.platforms.api_server import APIServerAdapter 125 126 adapter = APIServerAdapter( 127 PlatformConfig(enabled=True, extra={"host": "0.0.0.0", "key": "***"}) 128 ) 129 result = await adapter.connect() 130 assert result is False 131 132 def test_allows_loopback_with_placeholder_key(self): 133 """Loopback with a placeholder key is fine — not network-exposed.""" 134 from gateway.platforms.api_server import APIServerAdapter 135 from gateway.platforms.base import is_network_accessible 136 137 adapter = APIServerAdapter( 138 PlatformConfig(enabled=True, extra={"host": "127.0.0.1", "key": "changeme"}) 139 ) 140 # On loopback the placeholder guard doesn't fire 141 assert is_network_accessible(adapter._host) is False