diff --git a/gateway/config.py b/gateway/config.py index cc80cda40155..b43485fefeb9 100644 --- a/gateway/config.py +++ b/gateway/config.py @@ -2011,7 +2011,19 @@ def _apply_env_overrides(config: GatewayConfig) -> None: if api_server_enabled or api_server_key: if Platform.API_SERVER not in config.platforms: config.platforms[Platform.API_SERVER] = PlatformConfig() - config.platforms[Platform.API_SERVER].enabled = True + # Respect an explicit ``enabled: false`` in config.yaml (flagged by + # ``_enabled_explicit``). In multiplex mode a secondary profile's + # config.yaml pins ``platforms.api_server.enabled: false`` so it shares + # the default profile's listener instead of binding its own port. That + # profile still inherits the process-level env (including + # ``API_SERVER_KEY``); without this guard the env-var presence would + # force-enable the listener and trip the MultiplexConfigError check. + # Pop (don't read) the marker — the api_server branch is terminal (no + # later registry pass re-enables it), so this both consumes the flag and + # avoids reading it twice, matching the pop convention used elsewhere. + api_server_explicit = config.platforms[Platform.API_SERVER].extra.pop("_enabled_explicit", False) + if not api_server_explicit or config.platforms[Platform.API_SERVER].enabled: + config.platforms[Platform.API_SERVER].enabled = True if api_server_key: config.platforms[Platform.API_SERVER].extra["key"] = api_server_key if api_server_cors_origins: diff --git a/tests/gateway/test_config.py b/tests/gateway/test_config.py index 910961dd7bf1..0b272ab74eb8 100644 --- a/tests/gateway/test_config.py +++ b/tests/gateway/test_config.py @@ -2117,3 +2117,36 @@ class TestMultiplexProfilesConfig: "Explicit top-level false was overridden by nested true — " "loader must respect top-level precedence when key is present" ) + + +class TestApiServerEnvOverride: + def test_env_key_does_not_reenable_explicitly_disabled_api_server(self): + """An explicit ``platforms.api_server.enabled: false`` must survive + _apply_env_overrides() even when API_SERVER_KEY is present in the env. + + Regression: _apply_env_overrides() force-set api_server.enabled = True + whenever API_SERVER_KEY (or API_SERVER_ENABLED) was set. In multiplex + mode a secondary profile pins ``api_server.enabled: false`` so it shares + the default profile's listener instead of binding its own port, but it + still inherits the process-level API_SERVER_KEY. The unconditional + re-enable flipped it back on and tripped the MultiplexConfigError check. + + The fix honors the explicit disable, flagged by ``_enabled_explicit`` in + the platform's extra (set when the config.yaml pins enabled). + """ + config = GatewayConfig( + platforms={ + Platform.API_SERVER: PlatformConfig( + enabled=False, + extra={"_enabled_explicit": True}, + ), + }, + ) + + with patch.dict(os.environ, {"API_SERVER_KEY": "secret-key"}, clear=True): + _apply_env_overrides(config) + + # Explicit disable wins over the env-var presence. + assert config.platforms[Platform.API_SERVER].enabled is False + # The key is still wired through for the shared listener. + assert config.platforms[Platform.API_SERVER].extra.get("key") == "secret-key"