























@@ -33,8 +33,10 @@ class MockWebSocket {
3333terminateCalls = 0;
3434autoCloseOnClose = true;
3535readyState = MockWebSocket.CONNECTING;
36+readonly options: unknown;
363737-constructor(_url: string, _options?: unknown) {
38+constructor(_url: string, options?: unknown) {
39+this.options = options;
3840wsInstances.push(this);
3941}
4042@@ -179,17 +181,36 @@ beforeAll(async () => {
179181});
180182181183describe("GatewayClient security checks", () => {
182-const envSnapshot = captureEnv(["OPENCLAW_ALLOW_INSECURE_PRIVATE_WS"]);
184+const envSnapshot = captureEnv([
185+"OPENCLAW_ALLOW_INSECURE_PRIVATE_WS",
186+"OPENCLAW_PROXY_ACTIVE",
187+"OPENCLAW_PROXY_LOOPBACK_MODE",
188+"HTTP_PROXY",
189+"GLOBAL_AGENT_HTTP_PROXY",
190+"GLOBAL_AGENT_FORCE_GLOBAL_AGENT",
191+]);
183192184193beforeEach(() => {
185194envSnapshot.restore();
186195delete process.env.OPENCLAW_ALLOW_INSECURE_PRIVATE_WS;
196+delete process.env.OPENCLAW_PROXY_ACTIVE;
197+delete process.env.OPENCLAW_PROXY_LOOPBACK_MODE;
198+delete process.env.HTTP_PROXY;
199+delete process.env.GLOBAL_AGENT_HTTP_PROXY;
200+delete process.env.GLOBAL_AGENT_FORCE_GLOBAL_AGENT;
201+delete (global as Record<string, unknown>)["GLOBAL_AGENT"];
187202wsInstances.length = 0;
188203});
189204190205afterEach(() => {
191206envSnapshot.restore();
192207delete process.env.OPENCLAW_ALLOW_INSECURE_PRIVATE_WS;
208+delete process.env.OPENCLAW_PROXY_ACTIVE;
209+delete process.env.OPENCLAW_PROXY_LOOPBACK_MODE;
210+delete process.env.HTTP_PROXY;
211+delete process.env.GLOBAL_AGENT_HTTP_PROXY;
212+delete process.env.GLOBAL_AGENT_FORCE_GLOBAL_AGENT;
213+delete (global as Record<string, unknown>)["GLOBAL_AGENT"];
193214});
194215195216it("blocks ws:// to non-loopback addresses (CWE-319)", () => {
@@ -232,9 +253,82 @@ describe("GatewayClient security checks", () => {
232253233254expect(onConnectError).not.toHaveBeenCalled();
234255expect(wsInstances.length).toBe(1); // WebSocket created
256+expect(getLatestWs().options).not.toHaveProperty("agent");
235257client.stop();
236258});
237259260+it("bootstraps inherited managed proxy routing before proxy-mode loopback WebSocket creation", () => {
261+process.env.OPENCLAW_PROXY_ACTIVE = "1";
262+process.env.OPENCLAW_PROXY_LOOPBACK_MODE = "proxy";
263+process.env.HTTP_PROXY = "http://127.0.0.1:3128";
264+process.env.GLOBAL_AGENT_HTTP_PROXY = "http://127.0.0.1:3128";
265+const onConnectError = vi.fn();
266+const client = new GatewayClient({
267+url: "ws://127.0.0.1:18789",
268+ onConnectError,
269+});
270+271+client.start();
272+273+expect(onConnectError).not.toHaveBeenCalled();
274+expect(wsInstances.length).toBe(1);
275+expect(getLatestWs().options).not.toMatchObject({ agent: expect.any(Object) });
276+expect((global as Record<string, unknown>)["GLOBAL_AGENT"]).toEqual(
277+expect.objectContaining({
278+HTTP_PROXY: "http://127.0.0.1:3128",
279+HTTPS_PROXY: "http://127.0.0.1:3128",
280+}),
281+);
282+client.stop();
283+});
284+285+it("proxies ws:// loopback addresses when active proxy loopbackMode is proxy", async () => {
286+const { startProxy, stopProxy } = await import("../infra/net/proxy/proxy-lifecycle.js");
287+const handle = await startProxy({
288+enabled: true,
289+proxyUrl: "http://127.0.0.1:3128",
290+loopbackMode: "proxy",
291+});
292+const onConnectError = vi.fn();
293+const client = new GatewayClient({
294+url: "ws://127.0.0.1:18789",
295+ onConnectError,
296+});
297+298+try {
299+client.start();
300+301+expect(onConnectError).not.toHaveBeenCalled();
302+expect(wsInstances.length).toBe(1);
303+expect(getLatestWs().options).not.toMatchObject({ agent: expect.any(Object) });
304+} finally {
305+client.stop();
306+await stopProxy(handle);
307+}
308+});
309+310+it("blocks ws:// loopback addresses when active proxy loopbackMode is block", async () => {
311+const { startProxy, stopProxy } = await import("../infra/net/proxy/proxy-lifecycle.js");
312+const handle = await startProxy({
313+enabled: true,
314+proxyUrl: "http://127.0.0.1:3128",
315+loopbackMode: "block",
316+});
317+const onConnectError = vi.fn();
318+const client = new GatewayClient({
319+url: "ws://127.0.0.1:18789",
320+ onConnectError,
321+});
322+323+try {
324+expect(() => client.start()).toThrow("blocked by proxy.loopbackMode");
325+expect(wsInstances.length).toBe(0);
326+} finally {
327+client.stop();
328+await stopProxy(handle);
329+}
330+});
331+238332it("allows wss:// to any address", () => {
239333const onConnectError = vi.fn();
240334const client = new GatewayClient({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。