mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-22 14:41:34 +00:00
test: optimize gateway infra memory and security coverage
This commit is contained in:
@@ -11,14 +11,16 @@ import {
|
||||
} from "./net.js";
|
||||
|
||||
describe("resolveHostName", () => {
|
||||
it("returns hostname without port for IPv4/hostnames", () => {
|
||||
expect(resolveHostName("localhost:18789")).toBe("localhost");
|
||||
expect(resolveHostName("127.0.0.1:18789")).toBe("127.0.0.1");
|
||||
});
|
||||
|
||||
it("handles bracketed and unbracketed IPv6 loopback hosts", () => {
|
||||
expect(resolveHostName("[::1]:18789")).toBe("::1");
|
||||
expect(resolveHostName("::1")).toBe("::1");
|
||||
it("normalizes IPv4/hostname and IPv6 host forms", () => {
|
||||
const cases = [
|
||||
{ input: "localhost:18789", expected: "localhost" },
|
||||
{ input: "127.0.0.1:18789", expected: "127.0.0.1" },
|
||||
{ input: "[::1]:18789", expected: "::1" },
|
||||
{ input: "::1", expected: "::1" },
|
||||
] as const;
|
||||
for (const testCase of cases) {
|
||||
expect(resolveHostName(testCase.input), testCase.input).toBe(testCase.expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -204,27 +206,36 @@ describe("resolveClientIp", () => {
|
||||
});
|
||||
|
||||
describe("resolveGatewayListenHosts", () => {
|
||||
it("returns the input host when not loopback", async () => {
|
||||
const hosts = await resolveGatewayListenHosts("0.0.0.0", {
|
||||
canBindToHost: async () => {
|
||||
throw new Error("should not be called");
|
||||
it("resolves listen hosts for non-loopback and loopback variants", async () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "non-loopback host passthrough",
|
||||
host: "0.0.0.0",
|
||||
canBindToHost: async () => {
|
||||
throw new Error("should not be called");
|
||||
},
|
||||
expected: ["0.0.0.0"],
|
||||
},
|
||||
});
|
||||
expect(hosts).toEqual(["0.0.0.0"]);
|
||||
});
|
||||
{
|
||||
name: "loopback with IPv6 available",
|
||||
host: "127.0.0.1",
|
||||
canBindToHost: async () => true,
|
||||
expected: ["127.0.0.1", "::1"],
|
||||
},
|
||||
{
|
||||
name: "loopback with IPv6 unavailable",
|
||||
host: "127.0.0.1",
|
||||
canBindToHost: async () => false,
|
||||
expected: ["127.0.0.1"],
|
||||
},
|
||||
] as const;
|
||||
|
||||
it("adds ::1 when IPv6 loopback is available", async () => {
|
||||
const hosts = await resolveGatewayListenHosts("127.0.0.1", {
|
||||
canBindToHost: async () => true,
|
||||
});
|
||||
expect(hosts).toEqual(["127.0.0.1", "::1"]);
|
||||
});
|
||||
|
||||
it("keeps only IPv4 loopback when IPv6 is unavailable", async () => {
|
||||
const hosts = await resolveGatewayListenHosts("127.0.0.1", {
|
||||
canBindToHost: async () => false,
|
||||
});
|
||||
expect(hosts).toEqual(["127.0.0.1"]);
|
||||
for (const testCase of cases) {
|
||||
const hosts = await resolveGatewayListenHosts(testCase.host, {
|
||||
canBindToHost: testCase.canBindToHost,
|
||||
});
|
||||
expect(hosts, testCase.name).toEqual(testCase.expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -233,49 +244,48 @@ describe("pickPrimaryLanIPv4", () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns en0 IPv4 address when available", () => {
|
||||
vi.spyOn(os, "networkInterfaces").mockReturnValue({
|
||||
lo0: [
|
||||
{ address: "127.0.0.1", family: "IPv4", internal: true, netmask: "" },
|
||||
] as unknown as os.NetworkInterfaceInfo[],
|
||||
en0: [
|
||||
{ address: "192.168.1.42", family: "IPv4", internal: false, netmask: "" },
|
||||
] as unknown as os.NetworkInterfaceInfo[],
|
||||
});
|
||||
expect(pickPrimaryLanIPv4()).toBe("192.168.1.42");
|
||||
});
|
||||
it("prefers en0, then eth0, then any non-internal IPv4, otherwise undefined", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "prefers en0",
|
||||
interfaces: {
|
||||
lo0: [{ address: "127.0.0.1", family: "IPv4", internal: true, netmask: "" }],
|
||||
en0: [{ address: "192.168.1.42", family: "IPv4", internal: false, netmask: "" }],
|
||||
},
|
||||
expected: "192.168.1.42",
|
||||
},
|
||||
{
|
||||
name: "falls back to eth0",
|
||||
interfaces: {
|
||||
lo: [{ address: "127.0.0.1", family: "IPv4", internal: true, netmask: "" }],
|
||||
eth0: [{ address: "10.0.0.5", family: "IPv4", internal: false, netmask: "" }],
|
||||
},
|
||||
expected: "10.0.0.5",
|
||||
},
|
||||
{
|
||||
name: "falls back to any non-internal interface",
|
||||
interfaces: {
|
||||
lo: [{ address: "127.0.0.1", family: "IPv4", internal: true, netmask: "" }],
|
||||
wlan0: [{ address: "172.16.0.99", family: "IPv4", internal: false, netmask: "" }],
|
||||
},
|
||||
expected: "172.16.0.99",
|
||||
},
|
||||
{
|
||||
name: "no non-internal interface",
|
||||
interfaces: {
|
||||
lo: [{ address: "127.0.0.1", family: "IPv4", internal: true, netmask: "" }],
|
||||
},
|
||||
expected: undefined,
|
||||
},
|
||||
] as const;
|
||||
|
||||
it("returns eth0 IPv4 address when en0 is absent", () => {
|
||||
vi.spyOn(os, "networkInterfaces").mockReturnValue({
|
||||
lo: [
|
||||
{ address: "127.0.0.1", family: "IPv4", internal: true, netmask: "" },
|
||||
] as unknown as os.NetworkInterfaceInfo[],
|
||||
eth0: [
|
||||
{ address: "10.0.0.5", family: "IPv4", internal: false, netmask: "" },
|
||||
] as unknown as os.NetworkInterfaceInfo[],
|
||||
});
|
||||
expect(pickPrimaryLanIPv4()).toBe("10.0.0.5");
|
||||
});
|
||||
|
||||
it("falls back to any non-internal IPv4 interface", () => {
|
||||
vi.spyOn(os, "networkInterfaces").mockReturnValue({
|
||||
lo: [
|
||||
{ address: "127.0.0.1", family: "IPv4", internal: true, netmask: "" },
|
||||
] as unknown as os.NetworkInterfaceInfo[],
|
||||
wlan0: [
|
||||
{ address: "172.16.0.99", family: "IPv4", internal: false, netmask: "" },
|
||||
] as unknown as os.NetworkInterfaceInfo[],
|
||||
});
|
||||
expect(pickPrimaryLanIPv4()).toBe("172.16.0.99");
|
||||
});
|
||||
|
||||
it("returns undefined when only internal interfaces exist", () => {
|
||||
vi.spyOn(os, "networkInterfaces").mockReturnValue({
|
||||
lo: [
|
||||
{ address: "127.0.0.1", family: "IPv4", internal: true, netmask: "" },
|
||||
] as unknown as os.NetworkInterfaceInfo[],
|
||||
});
|
||||
expect(pickPrimaryLanIPv4()).toBeUndefined();
|
||||
for (const testCase of cases) {
|
||||
vi.spyOn(os, "networkInterfaces").mockReturnValue(
|
||||
testCase.interfaces as unknown as ReturnType<typeof os.networkInterfaces>,
|
||||
);
|
||||
expect(pickPrimaryLanIPv4(), testCase.name).toBe(testCase.expected);
|
||||
vi.restoreAllMocks();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -312,40 +322,28 @@ describe("isPrivateOrLoopbackAddress", () => {
|
||||
});
|
||||
|
||||
describe("isSecureWebSocketUrl", () => {
|
||||
describe("wss:// (TLS) URLs", () => {
|
||||
it("returns true for wss:// regardless of host", () => {
|
||||
expect(isSecureWebSocketUrl("wss://127.0.0.1:18789")).toBe(true);
|
||||
expect(isSecureWebSocketUrl("wss://localhost:18789")).toBe(true);
|
||||
expect(isSecureWebSocketUrl("wss://remote.example.com:18789")).toBe(true);
|
||||
expect(isSecureWebSocketUrl("wss://192.168.1.100:18789")).toBe(true);
|
||||
});
|
||||
});
|
||||
it("accepts secure websocket/loopback ws URLs and rejects unsafe inputs", () => {
|
||||
const cases = [
|
||||
{ input: "wss://127.0.0.1:18789", expected: true },
|
||||
{ input: "wss://localhost:18789", expected: true },
|
||||
{ input: "wss://remote.example.com:18789", expected: true },
|
||||
{ input: "wss://192.168.1.100:18789", expected: true },
|
||||
{ input: "ws://127.0.0.1:18789", expected: true },
|
||||
{ input: "ws://localhost:18789", expected: true },
|
||||
{ input: "ws://[::1]:18789", expected: true },
|
||||
{ input: "ws://127.0.0.42:18789", expected: true },
|
||||
{ input: "ws://remote.example.com:18789", expected: false },
|
||||
{ input: "ws://192.168.1.100:18789", expected: false },
|
||||
{ input: "ws://10.0.0.5:18789", expected: false },
|
||||
{ input: "ws://100.64.0.1:18789", expected: false },
|
||||
{ input: "not-a-url", expected: false },
|
||||
{ input: "", expected: false },
|
||||
{ input: "http://127.0.0.1:18789", expected: false },
|
||||
{ input: "https://127.0.0.1:18789", expected: false },
|
||||
] as const;
|
||||
|
||||
describe("ws:// (plaintext) URLs", () => {
|
||||
it("returns true for ws:// to loopback addresses", () => {
|
||||
expect(isSecureWebSocketUrl("ws://127.0.0.1:18789")).toBe(true);
|
||||
expect(isSecureWebSocketUrl("ws://localhost:18789")).toBe(true);
|
||||
expect(isSecureWebSocketUrl("ws://[::1]:18789")).toBe(true);
|
||||
expect(isSecureWebSocketUrl("ws://127.0.0.42:18789")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for ws:// to non-loopback addresses (CWE-319)", () => {
|
||||
expect(isSecureWebSocketUrl("ws://remote.example.com:18789")).toBe(false);
|
||||
expect(isSecureWebSocketUrl("ws://192.168.1.100:18789")).toBe(false);
|
||||
expect(isSecureWebSocketUrl("ws://10.0.0.5:18789")).toBe(false);
|
||||
expect(isSecureWebSocketUrl("ws://100.64.0.1:18789")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("invalid URLs", () => {
|
||||
it("returns false for invalid URLs", () => {
|
||||
expect(isSecureWebSocketUrl("not-a-url")).toBe(false);
|
||||
expect(isSecureWebSocketUrl("")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for non-WebSocket protocols", () => {
|
||||
expect(isSecureWebSocketUrl("http://127.0.0.1:18789")).toBe(false);
|
||||
expect(isSecureWebSocketUrl("https://127.0.0.1:18789")).toBe(false);
|
||||
});
|
||||
for (const testCase of cases) {
|
||||
expect(isSecureWebSocketUrl(testCase.input), testCase.input).toBe(testCase.expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user