




















@@ -4,13 +4,21 @@ import type { DeviceIdentity } from "../infra/device-identity.js";
44import { captureEnv } from "../test-utils/env.js";
55import { MIN_CLIENT_PROTOCOL_VERSION, PROTOCOL_VERSION } from "./protocol/index.js";
667+type MockLoggingConfig = {
8+redactPatterns?: string[];
9+redactSensitive?: "off" | "tools";
10+};
11+712const wsInstances = vi.hoisted((): MockWebSocket[] => []);
813const wsConstructorObservers = vi.hoisted((): Array<(url: string, options: unknown) => void> => []);
914const clearDeviceAuthTokenMock = vi.hoisted(() => vi.fn());
1015const loadDeviceAuthTokenMock = vi.hoisted(() => vi.fn());
1116const storeDeviceAuthTokenMock = vi.hoisted(() => vi.fn());
1217const logDebugMock = vi.hoisted(() => vi.fn());
1318const logErrorMock = vi.hoisted(() => vi.fn());
19+const readLoggingConfigMock = vi.hoisted(() =>
20+vi.fn<() => MockLoggingConfig | undefined>(() => undefined),
21+);
1422const {
1523 installGlobalProxyMock,
1624 proxylineRegisterBypassMock,
@@ -167,6 +175,15 @@ vi.mock("../logger.js", async () => {
167175};
168176});
169177178+vi.mock("../logging/config.js", async () => {
179+const actual =
180+await vi.importActual<typeof import("../logging/config.js")>("../logging/config.js");
181+return {
182+ ...actual,
183+readLoggingConfig: () => readLoggingConfigMock(),
184+};
185+});
186+170187type GatewayClientModule = typeof import("./client.js");
171188type GatewayClientInstance = InstanceType<GatewayClientModule["GatewayClient"]>;
172189@@ -820,6 +837,8 @@ describe("GatewayClient connect auth payload", () => {
820837clearDeviceAuthTokenMock.mockReset();
821838loadDeviceAuthTokenMock.mockReset();
822839storeDeviceAuthTokenMock.mockReset();
840+readLoggingConfigMock.mockReset();
841+readLoggingConfigMock.mockReturnValue(undefined);
823842logDebugMock.mockClear();
824843logErrorMock.mockClear();
825844});
@@ -1067,6 +1086,82 @@ describe("GatewayClient connect auth payload", () => {
10671086expect(ws.closeCalls).toBe(1);
10681087});
106910881089+it("redacts secret-bearing connect failure logs", async () => {
1090+const client = new GatewayClient({
1091+url: "ws://127.0.0.1:18789",
1092+token: "shared-token",
1093+deviceIdentity: null,
1094+});
1095+1096+const { ws, connect } = startClientAndConnect({ client });
1097+emitConnectFailure(
1098+ws,
1099+connect.id,
1100+{ code: "AUTH_UNAUTHORIZED" },
1101+"Authorization: Bearer sk-testsecret1234567890abcd wss://user:pass@gateway.example/ws?token=secret-token", // pragma: allowlist secret
1102+);
1103+1104+await vi.waitFor(() => {
1105+expect(logErrorMock).toHaveBeenCalledWith(expect.stringContaining("gateway connect failed:"));
1106+});
1107+const logged = String(logErrorMock.mock.calls.at(-1)?.[0] ?? "");
1108+expect(logged).toContain("Authorization: Bearer");
1109+expect(logged).not.toContain("sk-testsecret1234567890abcd");
1110+expect(logged).not.toContain("user:pass");
1111+expect(logged).not.toContain("secret-token");
1112+client.stop();
1113+});
1114+1115+it("preserves trailing diagnostics after redacted connect failure URL query params", async () => {
1116+const client = new GatewayClient({
1117+url: "ws://127.0.0.1:18789",
1118+token: "shared-token",
1119+deviceIdentity: null,
1120+});
1121+1122+const { ws, connect } = startClientAndConnect({ client });
1123+emitConnectFailure(
1124+ws,
1125+connect.id,
1126+{ code: "AUTH_UNAUTHORIZED" },
1127+"wss://gateway.example/ws?token=secret-token failed with 401 from remote gateway", // pragma: allowlist secret
1128+);
1129+1130+await vi.waitFor(() => {
1131+expect(logErrorMock).toHaveBeenCalledWith(expect.stringContaining("gateway connect failed:"));
1132+});
1133+const logged = String(logErrorMock.mock.calls.at(-1)?.[0] ?? "");
1134+expect(logged).toContain("wss://gateway.example/ws?token=*** failed with 401");
1135+expect(logged).toContain("from remote gateway");
1136+expect(logged).not.toContain("secret-token");
1137+client.stop();
1138+});
1139+1140+it("forces secret redaction for connect failure logs when general log redaction is off", async () => {
1141+readLoggingConfigMock.mockReturnValue({ redactSensitive: "off" });
1142+const client = new GatewayClient({
1143+url: "ws://127.0.0.1:18789",
1144+token: "shared-token",
1145+deviceIdentity: null,
1146+});
1147+1148+const { ws, connect } = startClientAndConnect({ client });
1149+emitConnectFailure(
1150+ws,
1151+connect.id,
1152+{ code: "AUTH_UNAUTHORIZED" },
1153+"Authorization: Bearer sk-disabledredaction1234567890abcd", // pragma: allowlist secret
1154+);
1155+1156+await vi.waitFor(() => {
1157+expect(logErrorMock).toHaveBeenCalledWith(expect.stringContaining("gateway connect failed:"));
1158+});
1159+const logged = String(logErrorMock.mock.calls.at(-1)?.[0] ?? "");
1160+expect(logged).toContain("Authorization: Bearer");
1161+expect(logged).not.toContain("sk-disabledredaction1234567890abcd");
1162+client.stop();
1163+});
1164+10701165it("uses explicit shared password and does not inject stored device token", () => {
10711166loadDeviceAuthTokenMock.mockReturnValue({ token: "stored-device-token" });
10721167const client = new GatewayClient({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。