diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index eae7eefb..0edc2e70 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -1149,13 +1149,13 @@ def serve( console.print(" [cyan]Session[/cyan] : api:default") console.print(f" [cyan]Timeout[/cyan] : {timeout}s") api_key = api_cfg.api_key.strip() if api_cfg.api_key else "" + if not api_key: + console.print( + "[red]Error: api_key is not set. " + "Set api.api_key in config to prevent unauthenticated API access.[/red]" + ) + raise typer.Exit(1) if host in {"0.0.0.0", "::"}: - if not api_key: - console.print( - "[red]Error: host is 0.0.0.0 (all interfaces) but api_key is not set. " - "Set api.api_key in config to prevent unauthenticated access.[/red]" - ) - raise typer.Exit(1) console.print( "[yellow]API is bound to all interfaces " "(authentication required).[/yellow]" diff --git a/tests/cli/test_commands.py b/tests/cli/test_commands.py index 3369f8bb..29c5813d 100644 --- a/tests/cli/test_commands.py +++ b/tests/cli/test_commands.py @@ -2806,6 +2806,7 @@ def test_serve_uses_api_config_defaults_and_workspace_override( config.api.host = "127.0.0.2" config.api.port = 18900 config.api.timeout = 45.0 + config.api.api_key = "secret" override_workspace = tmp_path / "override-workspace" seen: dict[str, object] = {} @@ -2821,7 +2822,7 @@ def test_serve_uses_api_config_defaults_and_workspace_override( assert seen["host"] == "127.0.0.2" assert seen["port"] == 18900 assert seen["request_timeout"] == 45.0 - assert seen["api_key"] == "" + assert seen["api_key"] == "secret" def test_trigger_cli_queues_message_in_workspace( @@ -2862,6 +2863,7 @@ def test_serve_cli_options_override_api_config(monkeypatch, tmp_path: Path) -> N config.api.host = "127.0.0.2" config.api.port = 18900 config.api.timeout = 45.0 + config.api.api_key = "secret" seen: dict[str, object] = {} _patch_serve_runtime(monkeypatch, config, seen) @@ -2885,7 +2887,21 @@ def test_serve_cli_options_override_api_config(monkeypatch, tmp_path: Path) -> N assert seen["host"] == "127.0.0.1" assert seen["port"] == 18901 assert seen["request_timeout"] == 46.0 - assert seen["api_key"] == "" + assert seen["api_key"] == "secret" + + +def test_serve_rejects_loopback_without_api_key(monkeypatch, tmp_path: Path) -> None: + config_file = _write_instance_config(tmp_path) + config = Config() + seen: dict[str, object] = {} + + _patch_serve_runtime(monkeypatch, config, seen) + + result = runner.invoke(app, ["serve", "--config", str(config_file)]) + + assert result.exit_code == 1 + assert "api_key is not set" in result.stdout + assert "api_app" not in seen def test_serve_passes_configured_api_key(monkeypatch, tmp_path: Path) -> None: