feat: WebSocket auth hardening and e2e tests#45
feat: WebSocket auth hardening and e2e tests#45CaddyGlow wants to merge 4 commits intofeat/codex-msaf-compatibilityfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens the Codex WebSocket authentication flow (denying unauthorized handshakes before accepting) and expands WebSocket-specific test coverage, including unit helpers and end-to-end integration validation across both v1 and legacy WS paths.
Changes:
- Deny unauthorized WebSocket handshakes pre-
accept(), and add per-connection request context + model alias restoration in WS streaming. - Improve bypass-mode behavior by injecting a mock handler into real adapters (per-plugin configurable), enabling Codex WS mock streaming while keeping the Codex adapter active.
- Add substantial WebSocket unit/integration/e2e coverage plus shared WS test data + validation helpers.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
ccproxy/plugins/codex/routes.py |
WebSocket auth denial before accept; WS request context, model alias restoration, mock streaming support. |
ccproxy/plugins/codex/adapter.py |
Route mock handling via mock_handler injection; keep Codex adapter active in bypass mode. |
ccproxy/plugins/codex/detection_service.py |
Merge partial detected prompt cache with fallback prompts. |
ccproxy/plugins/codex/plugin.py |
Opt Codex out of default bypass-mode MockAdapter swap (use_mock_adapter_in_bypass_mode = False). |
ccproxy/core/plugins/factories.py |
Add per-plugin bypass control and inject mock_handler into adapters when bypass mode is enabled. |
ccproxy/services/adapters/base.py |
Add mock_handler plumbing to base adapter init. |
ccproxy/services/adapters/mock_adapter.py |
Tighten return typing via casts for streaming paths. |
tests/unit/plugins/test_codex_detection.py |
Add unit test for prompt-cache merge behavior. |
tests/unit/core/test_provider_factory_bypass.py |
Update bypass-mode factory expectations for Codex (real adapter + injected mock handler). |
tests/plugins/codex/unit/test_routes.py |
New unit tests for WS helper behavior (settings resolution, payload sanitization, model alias restore). |
tests/plugins/codex/integration/test_codex_websocket.py |
New integration tests covering WS streaming, warmup behavior, bypass mock streaming, and auth denial. |
tests/integration/test_websocket_e2e.py |
New end-to-end WS suite + optional live-server WS tests. |
tests/helpers/test_data.py |
Add WS endpoint configs + request builders + terminal event sets. |
tests/helpers/e2e_validation.py |
Add WS event-sequence/content/warmup/error validation helpers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async def _prepare_mock_websocket_payload( | ||
| adapter: "CodexAdapter", | ||
| provider_payload: dict[str, Any], | ||
| request_context: RequestContext, | ||
| ) -> dict[str, Any]: | ||
| body_bytes = json.dumps(provider_payload).encode("utf-8") | ||
| body_bytes = await adapter._map_request_model(request_context, body_bytes) | ||
| payload = json.loads(body_bytes.decode("utf-8")) | ||
|
|
||
| if adapter._should_apply_detection_payload(): | ||
| payload = adapter._apply_request_template(payload) | ||
| detected_instructions = adapter._get_instructions() | ||
| else: | ||
| payload = adapter._normalize_input_messages(payload) | ||
| detected_instructions = "" | ||
|
|
||
| existing_instructions = payload.get("instructions") | ||
| if isinstance(existing_instructions, str) and existing_instructions: | ||
| instructions = ( | ||
| f"{detected_instructions}\n{existing_instructions}" | ||
| if detected_instructions | ||
| else existing_instructions | ||
| ) | ||
| else: | ||
| instructions = detected_instructions | ||
|
|
||
| if instructions: | ||
| payload["instructions"] = instructions | ||
| else: | ||
| payload.pop("instructions", None) | ||
|
|
||
| payload["stream"] = True | ||
| payload["store"] = False | ||
| return payload |
There was a problem hiding this comment.
In bypass mode the WebSocket path uses _prepare_mock_websocket_payload() instead of prepare_provider_request(), but it currently doesn’t apply the same payload sanitization as the real path (e.g., removing unsupported keys like max_tokens/temperature, filtering item_reference inputs, and stripping _-prefixed metadata fields). This makes bypass-mode WebSocket behavior diverge from normal WebSocket behavior and can leak internal metadata into mock streams. Consider reusing the same sanitization steps from CodexAdapter.prepare_provider_request() (minus auth/header work) so both paths normalize payloads consistently.
a06adec to
9c34087
Compare
397981a to
89e3a8a
Compare
Add MSAF-compatible mock response handling and bypass mode infrastructure: - Add OpenAI Responses format support to mock handler with proper SSE events - Add prompt text extraction for context-aware mock responses - Add format-aware mock adapter with target format detection - Add bypass mode to plugin factory with MockAdapter fallback - Add inject_detection_payload config toggle for generic API usage - Add openai_thinking_xml streaming configuration - Add format adapter streaming configuration support - Add MSAF integration and real library tests - Add bypass mode factory tests
Harden WebSocket authentication and add comprehensive testing: - Rewrite WebSocket auth to use denial responses before accepting - Add separate auth checks for missing and invalid tokens - Add WebSocket bypass/alias handling with mock response streaming - Add request context and model alias restoration for WebSocket events - Add WebSocket payload sanitization through adapter pipeline - Simplify detection prompt merging to always merge with fallback - Add use_mock_adapter_in_bypass_mode flag for per-plugin control - Pass mock_handler to adapter kwargs for integrated bypass handling - Add WebSocket e2e tests, route tests, and detection alias tests - Remove agent-framework dependency
Extract _sanitize_provider_body from prepare_provider_request so both the real and mock WebSocket paths apply the same Codex-specific cleanup: removing unsupported keys, filtering item_reference inputs, and stripping _-prefixed metadata fields.
9c34087 to
40f264e
Compare
169de0c to
98e8f3d
Compare
… errors _make_websocket_terminal_event was always emitting type "response.completed" even for errors, and omitting sequence_number. Per the OpenAI Responses API spec (ResponseFailedEvent model), error terminal events must use type "response.failed" and all stream events require a sequence_number field.
f2f2215 to
f387474
Compare
Summary
use_mock_adapter_in_bypass_modecontrol flagPR Stack
This is PR 3 of 3 split from #41. Stacked on #44. Merge in order:
Combined diff of all 3 PRs equals the original PR #41 diff exactly.
Test plan