diff --git a/src/langbot/pkg/box/connector.py b/src/langbot/pkg/box/connector.py index df3a0d7fa..e10e8b9ac 100644 --- a/src/langbot/pkg/box/connector.py +++ b/src/langbot/pkg/box/connector.py @@ -368,6 +368,10 @@ class BoxRuntimeConnector(ManagedRuntimeConnector): if not self._control_token and allow_generate: self._control_token = secrets.token_urlsafe(48) if not self._control_token: + if getattr(getattr(self.ap, 'deployment', None), 'mode', 'oss') == 'cloud': + raise BoxRuntimeUnavailableError( + f'{BOX_CONTROL_TOKEN_ENV} must be configured with a strong shared secret for a Cloud Box runtime' + ) return '' try: self._control_token = validate_control_token(self._control_token) diff --git a/src/langbot/pkg/plugin/connector.py b/src/langbot/pkg/plugin/connector.py index f2dbff418..0279ab285 100644 --- a/src/langbot/pkg/plugin/connector.py +++ b/src/langbot/pkg/plugin/connector.py @@ -264,6 +264,11 @@ class PluginRuntimeConnector(ManagedRuntimeConnector): if not self._control_token and allow_generate: self._control_token = secrets.token_urlsafe(48) if not self._control_token: + if self.runtime_profile == 'shared': + raise PluginRuntimeNotConnectedError( + f'{PLUGIN_RUNTIME_CONTROL_TOKEN_ENV} must be configured with a strong shared secret ' + 'for a Cloud Plugin Runtime' + ) return {} try: self._control_token = validate_runtime_secret( diff --git a/tests/unit_tests/box/test_box_connector.py b/tests/unit_tests/box/test_box_connector.py index 4d769f244..0949801c6 100644 --- a/tests/unit_tests/box/test_box_connector.py +++ b/tests/unit_tests/box/test_box_connector.py @@ -24,7 +24,7 @@ from langbot.pkg.box.connector import BoxRuntimeConnector _CONTROL_TOKEN = 'box-control-token-that-is-longer-than-32-bytes' -def make_app(logger: Mock, runtime_endpoint: str = ''): +def make_app(logger: Mock, runtime_endpoint: str = '', *, cloud: bool = False): return SimpleNamespace( logger=logger, workspace_service=SimpleNamespace(instance_uuid='instance-a'), @@ -42,6 +42,7 @@ def make_app(logger: Mock, runtime_endpoint: str = ''): } } ), + deployment=SimpleNamespace(mode='cloud' if cloud else 'oss'), ) @@ -315,6 +316,14 @@ def test_external_box_runtime_control_headers_are_tokenless_when_secret_is_unset assert connector.get_control_headers() == {BOX_INSTANCE_HEADER: 'instance-a'} +def test_cloud_box_runtime_rejects_missing_control_secret(monkeypatch: pytest.MonkeyPatch): + monkeypatch.delenv(BOX_CONTROL_TOKEN_ENV, raising=False) + connector = BoxRuntimeConnector(make_app(Mock(), runtime_endpoint='http://box-runtime:5410', cloud=True)) + + with pytest.raises(BoxRuntimeUnavailableError, match=BOX_CONTROL_TOKEN_ENV): + connector.get_control_headers() + + def test_external_box_runtime_rejects_invalid_configured_control_token(monkeypatch: pytest.MonkeyPatch): monkeypatch.setenv(BOX_CONTROL_TOKEN_ENV, 'too-short') connector = BoxRuntimeConnector(make_app(Mock(), runtime_endpoint='http://box-runtime:5410')) diff --git a/tests/unit_tests/plugin/test_connector_ping.py b/tests/unit_tests/plugin/test_connector_ping.py index 3b3aee016..d44b1ca2b 100644 --- a/tests/unit_tests/plugin/test_connector_ping.py +++ b/tests/unit_tests/plugin/test_connector_ping.py @@ -15,7 +15,7 @@ from langbot_plugin.runtime.security import ( ) -def make_connector() -> PluginRuntimeConnector: +def make_connector(*, cloud: bool = False) -> PluginRuntimeConnector: app = SimpleNamespace( logger=Mock(), instance_config=SimpleNamespace( @@ -34,6 +34,7 @@ def make_connector() -> PluginRuntimeConnector: 'space': {'url': ''}, } ), + deployment=SimpleNamespace(mode='cloud' if cloud else 'oss'), ) return PluginRuntimeConnector(app, AsyncMock()) @@ -332,6 +333,14 @@ def test_external_runtime_control_headers_are_empty_when_secret_is_unset(monkeyp assert connector._control_headers(allow_generate=False) == {} +def test_cloud_runtime_rejects_missing_control_secret(monkeypatch): + monkeypatch.delenv(PLUGIN_RUNTIME_CONTROL_TOKEN_ENV, raising=False) + connector = make_connector(cloud=True) + + with pytest.raises(PluginRuntimeNotConnectedError, match=PLUGIN_RUNTIME_CONTROL_TOKEN_ENV): + connector._control_headers(allow_generate=False) + + def test_local_runtime_control_headers_generate_ephemeral_secret(monkeypatch): monkeypatch.delenv(PLUGIN_RUNTIME_CONTROL_TOKEN_ENV, raising=False) connector = make_connector()