refactor(api-proxy): deduplicate guard enforcement between HTTP and WebSocket paths, fix 3 missing WebSocket guards#4950
Conversation
Extract guard descriptors into a shared `guards/common-guard-checks.js` factory (`buildCommonGuardChecks`) so that every security guard is enforced consistently on both the HTTP and WebSocket upgrade paths. Changes: - New `guards/common-guard-checks.js`: returns the array of guard descriptors (effective-tokens, max-runs, permission-denied, ai-credits, model-multiplier-cap, retired-model, unknown-model-ai-credits) shared by both proxy paths. - `proxy-request.js` `enforceGuards()`: replaced ~87 lines of inline guard array with a single call to `buildCommonGuardChecks()`. - `websocket-proxy.js`: replaced 4 duplicate inline if-blocks (lines 68-123) with a new `enforceWebSocketGuards()` that loops over the shared descriptor list. Also accepts the 3 previously-missing guard deps (`getModelMultiplierCapBlockState`, `getRetiredModelBlockState`, `checkUnknownModelRejection`) so they are now enforced on WebSocket connections too. - `proxy-request.js` `createProxyWebSocket` call: passes the 3 new guard deps to the WebSocket factory. - `server.websocket.test.js`: new 'security guards' describe block with 5 tests covering all 4 common guards on the WebSocket path plus a passing-case test. Closes #4789
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (1 files)
Coverage comparison generated by |
There was a problem hiding this comment.
Pull request overview
This PR refactors the api-proxy’s security-guard enforcement so the HTTP request path and the WebSocket upgrade path share a single canonical list of guard descriptors, reducing duplication and helping keep enforcement consistent between the two proxy entry points.
Changes:
- Added a shared
buildCommonGuardChecks(deps, model)factory to define the canonical set/order of common guard checks. - Refactored both the HTTP guard loop (
enforceGuardsinproxy-request.js) and the WebSocket upgrade guard enforcement (enforceWebSocketGuardsinwebsocket-proxy.js) to iterate the shared descriptor list. - Added WebSocket-focused guard tests to ensure guard enforcement is exercised on the upgrade path.
Show a summary per file
| File | Description |
|---|---|
| containers/api-proxy/websocket-proxy.js | Replaces duplicated inline WebSocket guard if blocks with a shared, data-driven guard loop. |
| containers/api-proxy/proxy-request.js | Replaces the inline HTTP guard descriptor array with buildCommonGuardChecks(...) and wires new deps into createProxyWebSocket. |
| containers/api-proxy/guards/common-guard-checks.js | Introduces the shared guard-descriptor factory used by both HTTP and WebSocket paths. |
| containers/api-proxy/server.websocket.test.js | Adds tests validating that guard enforcement runs on the WebSocket upgrade path. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 4/4 changed files
- Comments generated: 2
| * Model-specific guards (model_multiplier_cap, retired_model, | ||
| * unknown_model_ai_credits) are only included when `model` is non-null. For | ||
| * WebSocket upgrade requests there is no JSON request body, so callers should | ||
| * pass null and the model guards are skipped. |
| // These tests verify that all common security guards are enforced on the | ||
| // WebSocket upgrade path using the shared buildCommonGuardChecks factory. | ||
| // Guards are triggered by directly calling their apply functions (same | ||
| // technique used in guards/*.test.js unit tests). | ||
|
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — ✅ PASS
|
🔬 Smoke Test: Copilot PAT Auth — PASS
Auth mode: PAT (COPILOT_GITHUB_TOKEN) PR: refactor(api-proxy): deduplicate guard enforcement between HTTP and WebSocket paths, fix 3 missing WebSocket guards
|
🔍 Smoke Test Results — PASS
PR: refactor(api-proxy): deduplicate guard enforcement between HTTP and WebSocket paths, fix 3 missing WebSocket guards Overall: PASS ✅
|
Smoke Test: Copilot BYOK Direct Mode ✅ PASS✅ GitHub MCP connectivity Running in direct BYOK mode via cc
|
🔭 Smoke Test: API Proxy OpenTelemetry Tracing
All scenarios pass. OTEL tracing integration is functional.
|
Chroot Version Comparison Results
Overall: ❌ Not all tests passed — Python and Node.js versions differ between host and chroot environments.
|
Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "registry.npmjs.org"See Network Configuration for more information.
|
Smoke Test Results
Overall Status: FAIL Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "localhost"See Network Configuration for more information.
|
Smoke Test: GitHub Actions Services Connectivity
Overall: ❌ FAIL
|
|
|
The WebSocket upgrade path had 4 security guards copy-pasted as inline
if-blocks (~56 lines) while the HTTP path used a cleaner data-driven loop — and was silently enforcing 3 additional guards (model_multiplier_cap_exceeded,retired_model,unknown_model_ai_credits) that WebSocket connections completely bypassed.Changes
New:
guards/common-guard-checks.jsbuildCommonGuardChecks(deps, model)— a factory returning the canonical array of 7 guard descriptors shared by both proxy paths. Model-specific guards are included only whenmodelis non-null (WebSocket upgrades have no JSON body, so they passnulland skip those three).websocket-proxy.jsif-blocks (lines 68–123) withenforceWebSocketGuards(), a lightweight enforcer that writes raw HTTP error responses to a socket by iteratingbuildCommonGuardChecks().getModelMultiplierCapBlockState,getRetiredModelBlockState,checkUnknownModelRejection.proxy-request.jsenforceGuards()replaced ~87 lines of inline guard array with a singlebuildCommonGuardChecks()call.createProxyWebSocket()call extended to pass the 3 new deps.server.websocket.test.jsNew
proxyWebSocket security guardsdescribe block with 5 tests covering each common guard on the WebSocket path and a passing-case assertion.