Files
Cognitive_Agent/tests/e2e/test_http_request_security.py
2026-09-11 17:56:56 +08:00

177 lines
6.4 KiB
Python

from __future__ import annotations
import json
import threading
import urllib.error
import urllib.request
from email.message import Message
from http.server import ThreadingHTTPServer
from cognitive_agent.auth import AuthConfig, AuthRuntime
from cognitive_agent.server import SeatDemoHandler
def _auth(*, enabled: bool) -> AuthRuntime:
return AuthRuntime(
AuthConfig(
enabled=enabled,
username="test-user" if enabled else "",
password_hash="unused" if enabled else "",
session_secret=b"s" * 64 if enabled else b"",
session_ttl_seconds=3600,
login_attempts=5,
login_window_seconds=300,
requests_per_minute=120,
trust_proxy_headers=False,
)
)
class MutationSecurityHandler(SeatDemoHandler):
auth = _auth(enabled=False)
assistant_calls = 0
internal_calls = 0
last_hardware_bypass = True
def _handle_assistant_stream(self) -> None:
type(self).assistant_calls += 1
type(self).last_hardware_bypass = self._hardware_access_bypass_allowed()
self._read_json_body()
self._json({"handled": True})
def _handle_codex_vehicle_control(self) -> None:
type(self).internal_calls += 1
self._read_json_body()
self._json({"handled": True})
class TestMutationRequestSecurity:
@classmethod
def setup_class(cls) -> None:
cls.server = ThreadingHTTPServer(("127.0.0.1", 0), MutationSecurityHandler)
cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
cls.thread.start()
cls.authority = f"127.0.0.1:{cls.server.server_port}"
cls.base_url = f"http://{cls.authority}"
@classmethod
def teardown_class(cls) -> None:
cls.server.shutdown()
cls.server.server_close()
cls.thread.join(timeout=3)
def setup_method(self) -> None:
MutationSecurityHandler.assistant_calls = 0
MutationSecurityHandler.internal_calls = 0
MutationSecurityHandler.last_hardware_bypass = True
def post(
self,
path: str,
*,
content_type: str | None = "application/json",
origin: str | None = None,
) -> tuple[int, dict[str, object]]:
headers = {}
if content_type is not None:
headers["Content-Type"] = content_type
if origin is not None:
headers["Origin"] = origin
request = urllib.request.Request(
f"{self.base_url}{path}",
data=b"{}",
headers=headers,
method="POST",
)
try:
with urllib.request.urlopen(request, timeout=3) as response:
return response.status, json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as exc:
return exc.code, json.loads(exc.read().decode("utf-8"))
def test_same_origin_json_assistant_request_is_accepted(self) -> None:
status, body = self.post(
"/api/assistant/stream",
content_type="application/json; charset=utf-8",
origin=self.base_url,
)
assert status == 200
assert body == {"handled": True}
assert MutationSecurityHandler.assistant_calls == 1
def test_cross_origin_alias_is_rejected_before_assistant_execution(self) -> None:
status, body = self.post(
"/api/assistant/stream",
origin=f"http://localhost:{self.server.server_port}",
)
assert status == 403
assert body["error"] == "cross_origin_request"
assert MutationSecurityHandler.assistant_calls == 0
def test_origin_scheme_and_port_must_match(self) -> None:
for origin in (
f"https://{self.authority}",
f"http://127.0.0.1:{self.server.server_port + 1}",
"null",
):
status, body = self.post("/api/assistant/stream", origin=origin)
assert status == 403
assert body["error"] == "cross_origin_request"
assert MutationSecurityHandler.assistant_calls == 0
def test_non_json_assistant_request_is_rejected_before_body_parsing(self) -> None:
for content_type in (None, "text/plain", "application/x-www-form-urlencoded"):
status, body = self.post(
"/api/assistant/stream",
content_type=content_type,
origin=self.base_url,
)
assert status == 415
assert body["error"] == "unsupported_media_type"
assert MutationSecurityHandler.assistant_calls == 0
def test_no_origin_local_json_requests_remain_compatible(self) -> None:
assistant_status, _ = self.post("/api/assistant/stream")
internal_status, _ = self.post("/api/internal/codex/vehicle-control")
assert assistant_status == 200
assert internal_status == 200
assert MutationSecurityHandler.assistant_calls == 1
assert MutationSecurityHandler.internal_calls == 1
def test_auth_disabled_never_grants_implicit_hardware_bypass(self) -> None:
status, _ = self.post("/api/assistant/stream")
assert status == 200
assert MutationSecurityHandler.last_hardware_bypass is False
def test_hardware_bypass_requires_auth_enabled_and_direct_loopback(self) -> None:
handler = object.__new__(SeatDemoHandler)
handler.client_address = ("127.0.0.1", 12345)
handler.headers = Message()
handler.auth = _auth(enabled=False)
assert handler._hardware_access_bypass_allowed() is False
handler.auth = _auth(enabled=True)
assert handler._hardware_access_bypass_allowed() is False
session = handler.auth.sessions.issue("test-user")
handler.headers["Cookie"] = f"{handler.auth.config.cookie_name}={session}"
assert handler._hardware_access_bypass_allowed() is True
handler.headers["X-Forwarded-For"] = "203.0.113.10"
assert handler._hardware_access_bypass_allowed() is False
def test_wireless_passive_state_bypass_does_not_widen_control_bypass(self) -> None:
handler = object.__new__(SeatDemoHandler)
handler.client_address = ("127.0.0.1", 12345)
handler.headers = Message()
handler.auth = _auth(enabled=False)
assert handler._passive_state_access_bypass_allowed("wireless_mqtt") is True
assert handler._passive_state_access_bypass_allowed("wired_can") is False
assert handler._hardware_access_bypass_allowed() is False