"""Tests for web UI deployment and auth boundary (#435).""" import os import subprocess import sys import unittest from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from starlette.testclient import TestClient from webui.app import create_app from webui.deployment_boundary import ( assess_bind_host, runtime_assumptions, scan_text_for_client_secrets, ) class TestBindAssessment(unittest.TestCase): def test_loopback_is_safe(self): for host in ("127.0.0.1", "localhost", "::1"): with self.subTest(host=host): result = assess_bind_host(host) self.assertEqual(result.disposition, "safe") def test_all_interfaces_refused_by_default(self): for host in ("0.0.0.0", "::", "*"): with self.subTest(host=host): result = assess_bind_host(host) self.assertEqual(result.disposition, "refuse") def test_all_interfaces_allowed_with_override(self): prior = os.environ.pop("WEBUI_ALLOW_PUBLIC_BIND", None) try: os.environ["WEBUI_ALLOW_PUBLIC_BIND"] = "1" result = assess_bind_host("0.0.0.0") self.assertEqual(result.disposition, "warn") finally: os.environ.pop("WEBUI_ALLOW_PUBLIC_BIND", None) if prior is not None: os.environ["WEBUI_ALLOW_PUBLIC_BIND"] = prior def test_non_loopback_warns_without_override(self): prior = os.environ.pop("WEBUI_ALLOW_REMOTE_BIND", None) try: os.environ.pop("WEBUI_ALLOW_REMOTE_BIND", None) result = assess_bind_host("192.168.1.10") self.assertEqual(result.disposition, "warn") finally: if prior is not None: os.environ["WEBUI_ALLOW_REMOTE_BIND"] = prior def test_runtime_assumptions_exclude_secrets(self): assumptions = runtime_assumptions() blob = " ".join(str(v) for v in assumptions.values()) self.assertNotIn("GITEA_TOKEN", blob) self.assertIn("internal-operator-console", assumptions["deployment_mode"]) class TestDeploymentRoutes(unittest.TestCase): def setUp(self): self.client = TestClient(create_app(bind_host="127.0.0.1")) def test_health_includes_deployment_metadata(self): response = self.client.get("/health") self.assertEqual(response.status_code, 200) data = response.json() self.assertIn("deployment", data) deployment = data["deployment"] self.assertEqual(deployment["mode"], "internal-operator-console") self.assertEqual(deployment["mvp_auth"], "none") self.assertEqual(deployment["bind"]["disposition"], "safe") self.assertIn("runtime_assumptions", deployment) def test_rendered_pages_contain_no_embedded_secrets(self): for path in ("/", "/projects", "/prompts", "/queue"): with self.subTest(path=path): text = self.client.get(path).text self.assertEqual(scan_text_for_client_secrets(text), []) class TestStartupRefusal(unittest.TestCase): def test_refuses_all_interface_bind(self): env = {**os.environ, "WEBUI_HOST": "0.0.0.0"} env.pop("WEBUI_ALLOW_PUBLIC_BIND", None) repo_root = Path(__file__).resolve().parent.parent result = subprocess.run( [sys.executable, "-m", "webui"], cwd=repo_root, env=env, capture_output=True, text=True, timeout=3, ) self.assertEqual(result.returncode, 1) class TestClientSecretScanner(unittest.TestCase): def test_detects_token_like_content(self): findings = scan_text_for_client_secrets("var x = GITEA_TOKEN_PRGS") self.assertTrue(findings) if __name__ == "__main__": unittest.main()