





















@@ -1,5 +1,5 @@
11import type { IncomingMessage, ServerResponse } from "node:http";
2-import type { Socket } from "node:net";
2+import { connect, type Socket } from "node:net";
33import type { Duplex } from "node:stream";
44import { describe, expect, test } from "vitest";
55import { WebSocket, WebSocketServer } from "ws";
@@ -172,6 +172,50 @@ async function expectWsConnected(url: string, headers?: Record<string, string>):
172172});
173173}
174174175+async function sendRawHttpRequest(params: {
176+host: string;
177+port: number;
178+requestTarget: string;
179+headers?: readonly string[];
180+}): Promise<string> {
181+return new Promise<string>((resolve, reject) => {
182+const socket = connect({ host: params.host, port: params.port }, () => {
183+const headers = params.headers ?? ["Host: localhost", "Connection: close"];
184+socket.write([`GET ${params.requestTarget} HTTP/1.1`, ...headers, "", ""].join("\r\n"));
185+});
186+let response = "";
187+let settled = false;
188+const finish = (fn: () => void) => {
189+if (settled) {
190+return;
191+}
192+settled = true;
193+socket.setTimeout(0);
194+fn();
195+};
196+socket.setEncoding("utf8");
197+socket.setTimeout(WS_REJECT_TIMEOUT_MS, () => {
198+const error = new Error("timeout");
199+finish(() => {
200+socket.destroy(error);
201+reject(error);
202+});
203+});
204+socket.on("data", (chunk) => {
205+response += chunk;
206+});
207+socket.once("end", () => {
208+finish(() => resolve(response));
209+});
210+socket.once("close", () => {
211+finish(() => resolve(response));
212+});
213+socket.once("error", (err) => {
214+finish(() => reject(err));
215+});
216+});
217+}
218+175219function makeWsClient(params: {
176220connId: string;
177221clientIp: string;
@@ -408,6 +452,53 @@ describe("gateway plugin node capability auth", () => {
408452}, "openclaw-canvas-auth-test-");
409453}, 60_000);
410454455+test("rejects malformed raw HTTP request targets without disrupting gateway", async () => {
456+await withCanvasGatewayHarness({
457+resolvedAuth: tokenResolvedAuth,
458+handleHttpRequest: allowCanvasHostHttp,
459+run: async ({ listener }) => {
460+for (const requestTarget of ["//", "///", "//${jndi:ldap://example}.action"]) {
461+const response = await sendRawHttpRequest({
462+host: "127.0.0.1",
463+port: listener.port,
464+ requestTarget,
465+});
466+expect(response).toMatch(/^HTTP\/1\.1 401 /);
467+}
468+469+const res = await fetchCanvas(`http://127.0.0.1:${listener.port}${CANVAS_HOST_PATH}/`);
470+expect(res.status).toBe(401);
471+},
472+});
473+}, 60_000);
474+475+test("rejects malformed raw WebSocket upgrade targets without disrupting gateway", async () => {
476+await withCanvasGatewayHarness({
477+resolvedAuth: tokenResolvedAuth,
478+handleHttpRequest: allowCanvasHostHttp,
479+run: async ({ listener }) => {
480+for (const requestTarget of ["//", "///", "//${jndi:ldap://example}.action"]) {
481+const response = await sendRawHttpRequest({
482+host: "127.0.0.1",
483+port: listener.port,
484+ requestTarget,
485+headers: [
486+"Host: localhost",
487+"Upgrade: websocket",
488+"Connection: Upgrade",
489+"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==",
490+"Sec-WebSocket-Version: 13",
491+],
492+});
493+expect(response).toMatch(/^HTTP\/1\.1 401 /);
494+}
495+496+const res = await fetchCanvas(`http://127.0.0.1:${listener.port}${CANVAS_HOST_PATH}/`);
497+expect(res.status).toBe(401);
498+},
499+});
500+}, 60_000);
501+411502test("denies canvas auth when trusted proxy omits forwarded client headers", async () => {
412503await withLoopbackTrustedProxy(async () => {
413504await withCanvasGatewayHarness({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。