fix: Gateway canvas host bypasses auth and serves files unauthenticated
This commit is contained in:
@@ -10,9 +10,15 @@ import { createServer as createHttpsServer } from "node:https";
|
|||||||
import type { CanvasHostHandler } from "../canvas-host/server.js";
|
import type { CanvasHostHandler } from "../canvas-host/server.js";
|
||||||
import type { createSubsystemLogger } from "../logging/subsystem.js";
|
import type { createSubsystemLogger } from "../logging/subsystem.js";
|
||||||
import { resolveAgentAvatar } from "../agents/identity-avatar.js";
|
import { resolveAgentAvatar } from "../agents/identity-avatar.js";
|
||||||
import { handleA2uiHttpRequest } from "../canvas-host/a2ui.js";
|
import {
|
||||||
|
A2UI_PATH,
|
||||||
|
CANVAS_HOST_PATH,
|
||||||
|
CANVAS_WS_PATH,
|
||||||
|
handleA2uiHttpRequest,
|
||||||
|
} from "../canvas-host/a2ui.js";
|
||||||
import { loadConfig } from "../config/config.js";
|
import { loadConfig } from "../config/config.js";
|
||||||
import { handleSlackHttpRequest } from "../slack/http/index.js";
|
import { handleSlackHttpRequest } from "../slack/http/index.js";
|
||||||
|
import { authorizeGatewayConnect } from "./auth.js";
|
||||||
import {
|
import {
|
||||||
handleControlUiAvatarRequest,
|
handleControlUiAvatarRequest,
|
||||||
handleControlUiHttpRequest,
|
handleControlUiHttpRequest,
|
||||||
@@ -31,6 +37,8 @@ import {
|
|||||||
resolveHookChannel,
|
resolveHookChannel,
|
||||||
resolveHookDeliver,
|
resolveHookDeliver,
|
||||||
} from "./hooks.js";
|
} from "./hooks.js";
|
||||||
|
import { sendUnauthorized } from "./http-common.js";
|
||||||
|
import { getBearerToken } from "./http-utils.js";
|
||||||
import { handleOpenAiHttpRequest } from "./openai-http.js";
|
import { handleOpenAiHttpRequest } from "./openai-http.js";
|
||||||
import { handleOpenResponsesHttpRequest } from "./openresponses-http.js";
|
import { handleOpenResponsesHttpRequest } from "./openresponses-http.js";
|
||||||
import { handleToolsInvokeHttpRequest } from "./tools-invoke-http.js";
|
import { handleToolsInvokeHttpRequest } from "./tools-invoke-http.js";
|
||||||
@@ -60,6 +68,16 @@ function sendJson(res: ServerResponse, status: number, body: unknown) {
|
|||||||
res.end(JSON.stringify(body));
|
res.end(JSON.stringify(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCanvasPath(pathname: string): boolean {
|
||||||
|
return (
|
||||||
|
pathname === A2UI_PATH ||
|
||||||
|
pathname.startsWith(`${A2UI_PATH}/`) ||
|
||||||
|
pathname === CANVAS_HOST_PATH ||
|
||||||
|
pathname.startsWith(`${CANVAS_HOST_PATH}/`) ||
|
||||||
|
pathname === CANVAS_WS_PATH
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export type HooksRequestHandler = (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
|
export type HooksRequestHandler = (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
|
||||||
|
|
||||||
export function createHooksRequestHandler(
|
export function createHooksRequestHandler(
|
||||||
@@ -287,6 +305,20 @@ export function createGatewayHttpServer(opts: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (canvasHost) {
|
if (canvasHost) {
|
||||||
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
||||||
|
if (isCanvasPath(url.pathname)) {
|
||||||
|
const token = getBearerToken(req);
|
||||||
|
const authResult = await authorizeGatewayConnect({
|
||||||
|
auth: resolvedAuth,
|
||||||
|
connectAuth: token ? { token, password: token } : null,
|
||||||
|
req,
|
||||||
|
trustedProxies,
|
||||||
|
});
|
||||||
|
if (!authResult.ok) {
|
||||||
|
sendUnauthorized(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (await handleA2uiHttpRequest(req, res)) {
|
if (await handleA2uiHttpRequest(req, res)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -331,14 +363,41 @@ export function attachGatewayUpgradeHandler(opts: {
|
|||||||
httpServer: HttpServer;
|
httpServer: HttpServer;
|
||||||
wss: WebSocketServer;
|
wss: WebSocketServer;
|
||||||
canvasHost: CanvasHostHandler | null;
|
canvasHost: CanvasHostHandler | null;
|
||||||
|
resolvedAuth: import("./auth.js").ResolvedGatewayAuth;
|
||||||
}) {
|
}) {
|
||||||
const { httpServer, wss, canvasHost } = opts;
|
const { httpServer, wss, canvasHost, resolvedAuth } = opts;
|
||||||
httpServer.on("upgrade", (req, socket, head) => {
|
httpServer.on("upgrade", (req, socket, head) => {
|
||||||
if (canvasHost?.handleUpgrade(req, socket, head)) {
|
void (async () => {
|
||||||
return;
|
if (canvasHost) {
|
||||||
}
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
||||||
wss.handleUpgrade(req, socket, head, (ws) => {
|
if (url.pathname === CANVAS_WS_PATH) {
|
||||||
wss.emit("connection", ws, req);
|
const configSnapshot = loadConfig();
|
||||||
|
const token = getBearerToken(req);
|
||||||
|
const authResult = await authorizeGatewayConnect({
|
||||||
|
auth: resolvedAuth,
|
||||||
|
connectAuth: token ? { token, password: token } : null,
|
||||||
|
req,
|
||||||
|
trustedProxies: configSnapshot.gateway?.trustedProxies ?? [],
|
||||||
|
});
|
||||||
|
if (!authResult.ok) {
|
||||||
|
socket.write("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
|
||||||
|
socket.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canvasHost?.handleUpgrade(req, socket, head)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
||||||
|
wss.emit("connection", ws, req);
|
||||||
|
});
|
||||||
|
})().catch(() => {
|
||||||
|
try {
|
||||||
|
socket.destroy();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,7 +164,12 @@ export async function createGatewayRuntimeState(params: {
|
|||||||
maxPayload: MAX_PAYLOAD_BYTES,
|
maxPayload: MAX_PAYLOAD_BYTES,
|
||||||
});
|
});
|
||||||
for (const server of httpServers) {
|
for (const server of httpServers) {
|
||||||
attachGatewayUpgradeHandler({ httpServer: server, wss, canvasHost });
|
attachGatewayUpgradeHandler({
|
||||||
|
httpServer: server,
|
||||||
|
wss,
|
||||||
|
canvasHost,
|
||||||
|
resolvedAuth: params.resolvedAuth,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const clients = new Set<GatewayWsClient>();
|
const clients = new Set<GatewayWsClient>();
|
||||||
|
|||||||
Reference in New Issue
Block a user