mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 15:22:12 +03:00
fix(ws): codex Responses-over-WebSocket upgrade — clean handshake + bridge-secret auth
Two bugs made `wscat ws://host/v1/responses` fail with "Transfer-Encoding can't be present with Content-Length": 1. authz/management policy 401'd the proxy's own internal authenticate/prepare loopback call to /api/internal/codex-responses-ws (MANAGEMENT-classified, the per-process bridge secret wasn't recognized one layer up). Added a tightly-scoped carve-out: isValidWsBridgeRequest() honors a timing-safe sha256 match of OMNIROUTE_WS_BRIDGE_SECRET (x-omniroute-ws-bridge-secret header) for that exact internal path; the route still re-validates the secret. → auth now succeeds → 101. 2. On auth failure the proxy spread the internal fetch's response headers onto the raw upgrade socket — a chunked Transfer-Encoding + Next CSP/route-class headers collided with writeHttpError's Content-Length framing (and duplicated Content-Type via a case-mismatched spread). writeHttpError now strips framing + pipeline/security headers (case-insensitive), and the auth-fail callsite no longer forwards them. Regression test: tests/unit/responses-ws-proxy-headers.test.mjs (exports writeHttpError; asserts no TE+CL, single Content-Type, no CSP/route-class leak, safe headers forwarded).
This commit is contained in:
@@ -150,16 +150,47 @@ export function decodeClientFrames(
|
||||
};
|
||||
}
|
||||
|
||||
function writeHttpError(socket, status, body, headers = {}) {
|
||||
const WRITE_ERROR_RESERVED_HEADERS = new Set([
|
||||
// Framing — must never collide with our Content-Length default.
|
||||
"transfer-encoding",
|
||||
"content-length",
|
||||
"content-type",
|
||||
"connection",
|
||||
"keep-alive",
|
||||
// Next pipeline / security headers are meaningless on a raw JSON error socket
|
||||
// and must not leak from a forwarded internal-fetch response.
|
||||
"content-security-policy",
|
||||
"x-frame-options",
|
||||
"x-content-type-options",
|
||||
"referrer-policy",
|
||||
"permissions-policy",
|
||||
"strict-transport-security",
|
||||
"x-omniroute-route-class",
|
||||
"x-request-id",
|
||||
"date",
|
||||
]);
|
||||
|
||||
export function writeHttpError(socket, status, body, headers = {}) {
|
||||
if (!socket.writable || socket.destroyed) return;
|
||||
|
||||
const bodyBuffer = Buffer.from(body || "", "utf8");
|
||||
const statusText = STATUS_CODES[status] || "Error";
|
||||
// Strip any caller-supplied framing / duplicate-prone headers (case-insensitive)
|
||||
// so our Content-Length/Connection/Content-Type defaults always win. Forwarding
|
||||
// an upstream fetch's chunked Transfer-Encoding here would collide with
|
||||
// Content-Length ("Transfer-Encoding can't be present with Content-Length") and
|
||||
// break the client's HTTP parser on a raw upgrade socket.
|
||||
const safeHeaders = {};
|
||||
for (const [name, value] of Object.entries(headers || {})) {
|
||||
if (!WRITE_ERROR_RESERVED_HEADERS.has(String(name).toLowerCase())) {
|
||||
safeHeaders[name] = value;
|
||||
}
|
||||
}
|
||||
const responseHeaders = {
|
||||
Connection: "close",
|
||||
"Content-Length": String(bodyBuffer.length),
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
...headers,
|
||||
...safeHeaders,
|
||||
};
|
||||
|
||||
const head = [
|
||||
@@ -637,12 +668,11 @@ export function createResponsesWsProxy({
|
||||
headers: getAuthHeaders(req.url || pathname, req.headers),
|
||||
});
|
||||
if (!auth.ok) {
|
||||
writeHttpError(
|
||||
socket,
|
||||
auth.status,
|
||||
auth.text || "{}",
|
||||
Object.fromEntries(auth.headers.entries())
|
||||
);
|
||||
// Do NOT forward the internal fetch's response headers onto the raw
|
||||
// upgrade socket — they carry chunked transfer-encoding + Next security
|
||||
// headers that collide with writeHttpError's Content-Length framing.
|
||||
// The sanitized JSON body alone is enough for the client.
|
||||
writeHttpError(socket, auth.status, auth.text || "{}");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user