mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
CI: restore main detect-secrets scan (#38438)
* Tests: stabilize detect-secrets fixtures * Tests: fix rebased detect-secrets false positives * Docs: keep snippets valid under detect-secrets * Tests: finalize detect-secrets false-positive fixes * Tests: reduce detect-secrets false positives * Tests: keep detect-secrets pragmas inline * Tests: remediate next detect-secrets batch * Tests: tighten detect-secrets allowlists * Tests: stabilize detect-secrets formatter drift
This commit is contained in:
@@ -2391,11 +2391,11 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
});
|
||||
|
||||
const accountA: ResolvedBlueBubblesAccount = {
|
||||
...createMockAccount({ dmHistoryLimit: 3, password: "password-a" }),
|
||||
...createMockAccount({ dmHistoryLimit: 3, password: "password-a" }), // pragma: allowlist secret
|
||||
accountId: "acc-a",
|
||||
};
|
||||
const accountB: ResolvedBlueBubblesAccount = {
|
||||
...createMockAccount({ dmHistoryLimit: 3, password: "password-b" }),
|
||||
...createMockAccount({ dmHistoryLimit: 3, password: "password-b" }), // pragma: allowlist secret
|
||||
accountId: "acc-b",
|
||||
};
|
||||
const config: OpenClawConfig = {};
|
||||
|
||||
@@ -166,7 +166,7 @@ function createMockAccount(
|
||||
configured: true,
|
||||
config: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test-password",
|
||||
password: "test-password", // pragma: allowlist secret
|
||||
dmPolicy: "open",
|
||||
groupPolicy: "open",
|
||||
allowFrom: [],
|
||||
@@ -240,15 +240,6 @@ function getFirstDispatchCall(): DispatchReplyParams {
|
||||
}
|
||||
|
||||
describe("BlueBubbles webhook monitor", () => {
|
||||
const WEBHOOK_PATH = "/bluebubbles-webhook";
|
||||
const BASE_WEBHOOK_MESSAGE_DATA = {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
} as const;
|
||||
|
||||
let unregister: () => void;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -270,144 +261,122 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
unregister?.();
|
||||
});
|
||||
|
||||
function createWebhookPayload(
|
||||
dataOverrides: Record<string, unknown> = {},
|
||||
): Record<string, unknown> {
|
||||
return {
|
||||
type: "new-message",
|
||||
data: {
|
||||
...BASE_WEBHOOK_MESSAGE_DATA,
|
||||
...dataOverrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createWebhookTargetDeps(core?: PluginRuntime): {
|
||||
config: OpenClawConfig;
|
||||
core: PluginRuntime;
|
||||
runtime: {
|
||||
log: ReturnType<typeof vi.fn<(message: string) => void>>;
|
||||
error: ReturnType<typeof vi.fn<(message: string) => void>>;
|
||||
};
|
||||
} {
|
||||
const resolvedCore = core ?? createMockRuntime();
|
||||
setBlueBubblesRuntime(resolvedCore);
|
||||
return {
|
||||
config: {},
|
||||
core: resolvedCore,
|
||||
runtime: {
|
||||
log: vi.fn<(message: string) => void>(),
|
||||
error: vi.fn<(message: string) => void>(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function registerWebhookTarget(
|
||||
params: {
|
||||
account?: ResolvedBlueBubblesAccount;
|
||||
config?: OpenClawConfig;
|
||||
core?: PluginRuntime;
|
||||
runtime?: {
|
||||
log: ReturnType<typeof vi.fn<(message: string) => void>>;
|
||||
error: ReturnType<typeof vi.fn<(message: string) => void>>;
|
||||
};
|
||||
path?: string;
|
||||
statusSink?: Parameters<typeof registerBlueBubblesWebhookTarget>[0]["statusSink"];
|
||||
trackForCleanup?: boolean;
|
||||
} = {},
|
||||
): {
|
||||
config: OpenClawConfig;
|
||||
core: PluginRuntime;
|
||||
runtime: {
|
||||
log: ReturnType<typeof vi.fn<(message: string) => void>>;
|
||||
error: ReturnType<typeof vi.fn<(message: string) => void>>;
|
||||
};
|
||||
stop: () => void;
|
||||
} {
|
||||
const deps =
|
||||
params.config && params.core && params.runtime
|
||||
? { config: params.config, core: params.core, runtime: params.runtime }
|
||||
: createWebhookTargetDeps(params.core);
|
||||
const stop = registerBlueBubblesWebhookTarget({
|
||||
account: params.account ?? createMockAccount(),
|
||||
...deps,
|
||||
path: params.path ?? WEBHOOK_PATH,
|
||||
statusSink: params.statusSink,
|
||||
});
|
||||
if (params.trackForCleanup !== false) {
|
||||
unregister = stop;
|
||||
}
|
||||
return { ...deps, stop };
|
||||
}
|
||||
|
||||
async function sendWebhookRequest(params: {
|
||||
method?: string;
|
||||
url?: string;
|
||||
body?: unknown;
|
||||
headers?: Record<string, string>;
|
||||
remoteAddress?: string;
|
||||
}): Promise<{
|
||||
req: IncomingMessage;
|
||||
res: ServerResponse & { body: string; statusCode: number };
|
||||
handled: boolean;
|
||||
}> {
|
||||
const req = createMockRequest(
|
||||
params.method ?? "POST",
|
||||
params.url ?? WEBHOOK_PATH,
|
||||
params.body ?? createWebhookPayload(),
|
||||
params.headers,
|
||||
);
|
||||
if (params.remoteAddress) {
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
||||
remoteAddress: params.remoteAddress,
|
||||
};
|
||||
}
|
||||
const res = createMockResponse();
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
return { req, res, handled };
|
||||
}
|
||||
|
||||
describe("webhook parsing + auth handling", () => {
|
||||
it("rejects non-POST requests", async () => {
|
||||
registerWebhookTarget();
|
||||
const { handled, res } = await sendWebhookRequest({
|
||||
method: "GET",
|
||||
body: {},
|
||||
const account = createMockAccount();
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const req = createMockRequest("GET", "/bluebubbles-webhook", {});
|
||||
const res = createMockResponse();
|
||||
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(405);
|
||||
});
|
||||
|
||||
it("accepts POST requests with valid JSON payload", async () => {
|
||||
registerWebhookTarget();
|
||||
const { handled, res } = await sendWebhookRequest({
|
||||
body: createWebhookPayload({ date: Date.now() }),
|
||||
const account = createMockAccount();
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const payload = {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
date: Date.now(),
|
||||
},
|
||||
};
|
||||
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
||||
const res = createMockResponse();
|
||||
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toBe("ok");
|
||||
});
|
||||
|
||||
it("rejects requests with invalid JSON", async () => {
|
||||
registerWebhookTarget();
|
||||
const { handled, res } = await sendWebhookRequest({
|
||||
body: "invalid json {{",
|
||||
const account = createMockAccount();
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook", "invalid json {{");
|
||||
const res = createMockResponse();
|
||||
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it("accepts URL-encoded payload wrappers", async () => {
|
||||
registerWebhookTarget();
|
||||
const payload = createWebhookPayload({ date: Date.now() });
|
||||
const account = createMockAccount();
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const payload = {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
date: Date.now(),
|
||||
},
|
||||
};
|
||||
const encodedBody = new URLSearchParams({
|
||||
payload: JSON.stringify(payload),
|
||||
}).toString();
|
||||
|
||||
const { handled, res } = await sendWebhookRequest({ body: encodedBody });
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook", encodedBody);
|
||||
const res = createMockResponse();
|
||||
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(200);
|
||||
@@ -417,12 +386,23 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
it("returns 408 when request body times out (Slow-Loris protection)", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
registerWebhookTarget();
|
||||
const account = createMockAccount();
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
// Create a request that never sends data or ends (simulates slow-loris)
|
||||
const req = new EventEmitter() as IncomingMessage;
|
||||
req.method = "POST";
|
||||
req.url = `${WEBHOOK_PATH}?password=test-password`;
|
||||
req.url = "/bluebubbles-webhook?password=test-password";
|
||||
req.headers = {};
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
||||
remoteAddress: "127.0.0.1",
|
||||
@@ -446,13 +426,22 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
});
|
||||
|
||||
it("rejects unauthorized requests before reading the body", async () => {
|
||||
registerWebhookTarget({
|
||||
account: createMockAccount({ password: "secret-token" }),
|
||||
const account = createMockAccount({ password: "secret-token" });
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const req = new EventEmitter() as IncomingMessage;
|
||||
req.method = "POST";
|
||||
req.url = `${WEBHOOK_PATH}?password=wrong-token`;
|
||||
req.url = "/bluebubbles-webhook?password=wrong-token";
|
||||
req.headers = {};
|
||||
const onSpy = vi.spyOn(req, "on");
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
||||
@@ -468,43 +457,112 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
});
|
||||
|
||||
it("authenticates via password query parameter", async () => {
|
||||
registerWebhookTarget({
|
||||
account: createMockAccount({ password: "secret-token" }),
|
||||
const account = createMockAccount({ password: "secret-token" });
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
// Mock non-localhost request
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook?password=secret-token", {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
},
|
||||
});
|
||||
const { handled, res } = await sendWebhookRequest({
|
||||
url: `${WEBHOOK_PATH}?password=secret-token`,
|
||||
body: createWebhookPayload(),
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
||||
remoteAddress: "192.168.1.100",
|
||||
};
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const res = createMockResponse();
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
it("authenticates via x-password header", async () => {
|
||||
registerWebhookTarget({
|
||||
account: createMockAccount({ password: "secret-token" }),
|
||||
});
|
||||
const { handled, res } = await sendWebhookRequest({
|
||||
body: createWebhookPayload(),
|
||||
headers: { "x-password": "secret-token" },
|
||||
const account = createMockAccount({ password: "secret-token" });
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
const req = createMockRequest(
|
||||
"POST",
|
||||
"/bluebubbles-webhook",
|
||||
{
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
},
|
||||
},
|
||||
{ "x-password": "secret-token" }, // pragma: allowlist secret
|
||||
);
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
||||
remoteAddress: "192.168.1.100",
|
||||
};
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const res = createMockResponse();
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
it("rejects unauthorized requests with wrong password", async () => {
|
||||
registerWebhookTarget({
|
||||
account: createMockAccount({ password: "secret-token" }),
|
||||
const account = createMockAccount({ password: "secret-token" });
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook?password=wrong-token", {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
},
|
||||
});
|
||||
const { handled, res } = await sendWebhookRequest({
|
||||
url: `${WEBHOOK_PATH}?password=wrong-token`,
|
||||
body: createWebhookPayload(),
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
||||
remoteAddress: "192.168.1.100",
|
||||
};
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const res = createMockResponse();
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(401);
|
||||
});
|
||||
@@ -512,37 +570,50 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
it("rejects ambiguous routing when multiple targets match the same password", async () => {
|
||||
const accountA = createMockAccount({ password: "secret-token" });
|
||||
const accountB = createMockAccount({ password: "secret-token" });
|
||||
const { config, core, runtime } = createWebhookTargetDeps();
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
const sinkA = vi.fn();
|
||||
const sinkB = vi.fn();
|
||||
|
||||
const unregisterA = registerWebhookTarget({
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook?password=secret-token", {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
},
|
||||
});
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
||||
remoteAddress: "192.168.1.100",
|
||||
};
|
||||
|
||||
const unregisterA = registerBlueBubblesWebhookTarget({
|
||||
account: accountA,
|
||||
config,
|
||||
runtime,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
trackForCleanup: false,
|
||||
path: "/bluebubbles-webhook",
|
||||
statusSink: sinkA,
|
||||
}).stop;
|
||||
const unregisterB = registerWebhookTarget({
|
||||
});
|
||||
const unregisterB = registerBlueBubblesWebhookTarget({
|
||||
account: accountB,
|
||||
config,
|
||||
runtime,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
trackForCleanup: false,
|
||||
path: "/bluebubbles-webhook",
|
||||
statusSink: sinkB,
|
||||
}).stop;
|
||||
});
|
||||
unregister = () => {
|
||||
unregisterA();
|
||||
unregisterB();
|
||||
};
|
||||
|
||||
const { handled, res } = await sendWebhookRequest({
|
||||
url: `${WEBHOOK_PATH}?password=secret-token`,
|
||||
body: createWebhookPayload(),
|
||||
remoteAddress: "192.168.1.100",
|
||||
});
|
||||
const res = createMockResponse();
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(401);
|
||||
@@ -553,37 +624,50 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
it("ignores targets without passwords when a password-authenticated target matches", async () => {
|
||||
const accountStrict = createMockAccount({ password: "secret-token" });
|
||||
const accountWithoutPassword = createMockAccount({ password: undefined });
|
||||
const { config, core, runtime } = createWebhookTargetDeps();
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
const sinkStrict = vi.fn();
|
||||
const sinkWithoutPassword = vi.fn();
|
||||
|
||||
const unregisterStrict = registerWebhookTarget({
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook?password=secret-token", {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
},
|
||||
});
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
||||
remoteAddress: "192.168.1.100",
|
||||
};
|
||||
|
||||
const unregisterStrict = registerBlueBubblesWebhookTarget({
|
||||
account: accountStrict,
|
||||
config,
|
||||
runtime,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
trackForCleanup: false,
|
||||
path: "/bluebubbles-webhook",
|
||||
statusSink: sinkStrict,
|
||||
}).stop;
|
||||
const unregisterNoPassword = registerWebhookTarget({
|
||||
});
|
||||
const unregisterNoPassword = registerBlueBubblesWebhookTarget({
|
||||
account: accountWithoutPassword,
|
||||
config,
|
||||
runtime,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
trackForCleanup: false,
|
||||
path: "/bluebubbles-webhook",
|
||||
statusSink: sinkWithoutPassword,
|
||||
}).stop;
|
||||
});
|
||||
unregister = () => {
|
||||
unregisterStrict();
|
||||
unregisterNoPassword();
|
||||
};
|
||||
|
||||
const { handled, res } = await sendWebhookRequest({
|
||||
url: `${WEBHOOK_PATH}?password=secret-token`,
|
||||
body: createWebhookPayload(),
|
||||
remoteAddress: "192.168.1.100",
|
||||
});
|
||||
const res = createMockResponse();
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(200);
|
||||
@@ -593,20 +677,34 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
|
||||
it("requires authentication for loopback requests when password is configured", async () => {
|
||||
const account = createMockAccount({ password: "secret-token" });
|
||||
const { config, core, runtime } = createWebhookTargetDeps();
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
for (const remoteAddress of ["127.0.0.1", "::1", "::ffff:127.0.0.1"]) {
|
||||
const loopbackUnregister = registerWebhookTarget({
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook", {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
},
|
||||
});
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
||||
remoteAddress,
|
||||
};
|
||||
|
||||
const loopbackUnregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
trackForCleanup: false,
|
||||
}).stop;
|
||||
|
||||
const { handled, res } = await sendWebhookRequest({
|
||||
body: createWebhookPayload(),
|
||||
remoteAddress,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const res = createMockResponse();
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(401);
|
||||
|
||||
@@ -615,8 +713,17 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
});
|
||||
|
||||
it("rejects targets without passwords for loopback and proxied-looking requests", async () => {
|
||||
registerWebhookTarget({
|
||||
account: createMockAccount({ password: undefined }),
|
||||
const account = createMockAccount({ password: undefined });
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const headerVariants: Record<string, string>[] = [
|
||||
@@ -625,11 +732,26 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
{ host: "localhost", forwarded: "for=203.0.113.10;proto=https;host=example.com" },
|
||||
];
|
||||
for (const headers of headerVariants) {
|
||||
const { handled, res } = await sendWebhookRequest({
|
||||
body: createWebhookPayload(),
|
||||
const req = createMockRequest(
|
||||
"POST",
|
||||
"/bluebubbles-webhook",
|
||||
{
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
},
|
||||
},
|
||||
headers,
|
||||
);
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
||||
remoteAddress: "127.0.0.1",
|
||||
});
|
||||
};
|
||||
const res = createMockResponse();
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(401);
|
||||
}
|
||||
@@ -648,18 +770,36 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
const { resolveChatGuidForTarget } = await import("./send.js");
|
||||
vi.mocked(resolveChatGuidForTarget).mockClear();
|
||||
|
||||
registerWebhookTarget({
|
||||
account: createMockAccount({ groupPolicy: "open" }),
|
||||
const account = createMockAccount({ groupPolicy: "open" });
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
await sendWebhookRequest({
|
||||
body: createWebhookPayload({
|
||||
const payload = {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello from group",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: true,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
chatId: "123",
|
||||
date: Date.now(),
|
||||
}),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
||||
const res = createMockResponse();
|
||||
|
||||
await handleBlueBubblesWebhookRequest(req, res);
|
||||
await flushAsync();
|
||||
|
||||
expect(resolveChatGuidForTarget).toHaveBeenCalledWith(
|
||||
@@ -679,18 +819,36 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
return EMPTY_DISPATCH_RESULT;
|
||||
});
|
||||
|
||||
registerWebhookTarget({
|
||||
account: createMockAccount({ groupPolicy: "open" }),
|
||||
const account = createMockAccount({ groupPolicy: "open" });
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
await sendWebhookRequest({
|
||||
body: createWebhookPayload({
|
||||
const payload = {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello from group",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: true,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
chat: { chatGuid: "iMessage;+;chat123456" },
|
||||
date: Date.now(),
|
||||
}),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
||||
const res = createMockResponse();
|
||||
|
||||
await handleBlueBubblesWebhookRequest(req, res);
|
||||
await flushAsync();
|
||||
|
||||
expect(resolveChatGuidForTarget).not.toHaveBeenCalled();
|
||||
|
||||
@@ -329,13 +329,13 @@ describe("diagnostics-otel service", () => {
|
||||
|
||||
test("redacts sensitive data from log attributes before export", async () => {
|
||||
const emitCall = await emitAndCaptureLog({
|
||||
0: '{"token":"ghp_abcdefghijklmnopqrstuvwxyz123456"}',
|
||||
0: '{"token":"ghp_abcdefghijklmnopqrstuvwxyz123456"}', // pragma: allowlist secret
|
||||
1: "auth configured",
|
||||
_meta: { logLevelName: "DEBUG", date: new Date() },
|
||||
});
|
||||
|
||||
const tokenAttr = emitCall?.attributes?.["openclaw.token"];
|
||||
expect(tokenAttr).not.toBe("ghp_abcdefghijklmnopqrstuvwxyz123456");
|
||||
expect(tokenAttr).not.toBe("ghp_abcdefghijklmnopqrstuvwxyz123456"); // pragma: allowlist secret
|
||||
if (typeof tokenAttr === "string") {
|
||||
expect(tokenAttr).toContain("…");
|
||||
}
|
||||
@@ -349,7 +349,7 @@ describe("diagnostics-otel service", () => {
|
||||
emitDiagnosticEvent({
|
||||
type: "session.state",
|
||||
state: "waiting",
|
||||
reason: "token=ghp_abcdefghijklmnopqrstuvwxyz123456",
|
||||
reason: "token=ghp_abcdefghijklmnopqrstuvwxyz123456", // pragma: allowlist secret
|
||||
});
|
||||
|
||||
const sessionCounter = telemetryState.counters.get("openclaw.session.state");
|
||||
@@ -362,7 +362,7 @@ describe("diagnostics-otel service", () => {
|
||||
const attrs = sessionCounter?.add.mock.calls[0]?.[1] as Record<string, unknown> | undefined;
|
||||
expect(typeof attrs?.["openclaw.reason"]).toBe("string");
|
||||
expect(String(attrs?.["openclaw.reason"])).not.toContain(
|
||||
"ghp_abcdefghijklmnopqrstuvwxyz123456",
|
||||
"ghp_abcdefghijklmnopqrstuvwxyz123456", // pragma: allowlist secret
|
||||
);
|
||||
await service.stop?.(ctx);
|
||||
});
|
||||
|
||||
@@ -45,8 +45,8 @@ describe("resolveDefaultFeishuAccountId", () => {
|
||||
feishu: {
|
||||
defaultAccount: "router-d",
|
||||
accounts: {
|
||||
default: { appId: "cli_default", appSecret: "secret_default" },
|
||||
"router-d": { appId: "cli_router", appSecret: "secret_router" },
|
||||
default: { appId: "cli_default", appSecret: "secret_default" }, // pragma: allowlist secret
|
||||
"router-d": { appId: "cli_router", appSecret: "secret_router" }, // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -61,7 +61,7 @@ describe("resolveDefaultFeishuAccountId", () => {
|
||||
feishu: {
|
||||
defaultAccount: "Router D",
|
||||
accounts: {
|
||||
"router-d": { appId: "cli_router", appSecret: "secret_router" },
|
||||
"router-d": { appId: "cli_router", appSecret: "secret_router" }, // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -76,8 +76,8 @@ describe("resolveDefaultFeishuAccountId", () => {
|
||||
feishu: {
|
||||
defaultAccount: "router-d",
|
||||
accounts: {
|
||||
default: { appId: "cli_default", appSecret: "secret_default" },
|
||||
zeta: { appId: "cli_zeta", appSecret: "secret_zeta" },
|
||||
default: { appId: "cli_default", appSecret: "secret_default" }, // pragma: allowlist secret
|
||||
zeta: { appId: "cli_zeta", appSecret: "secret_zeta" }, // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -91,8 +91,8 @@ describe("resolveDefaultFeishuAccountId", () => {
|
||||
channels: {
|
||||
feishu: {
|
||||
accounts: {
|
||||
default: { appId: "cli_default", appSecret: "secret_default" },
|
||||
zeta: { appId: "cli_zeta", appSecret: "secret_zeta" },
|
||||
default: { appId: "cli_default", appSecret: "secret_default" }, // pragma: allowlist secret
|
||||
zeta: { appId: "cli_zeta", appSecret: "secret_zeta" }, // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -119,7 +119,7 @@ describe("resolveDefaultFeishuAccountId", () => {
|
||||
channels: {
|
||||
feishu: {
|
||||
accounts: {
|
||||
default: { appId: "cli_default", appSecret: "secret_default" },
|
||||
default: { appId: "cli_default", appSecret: "secret_default" }, // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -178,7 +178,7 @@ describe("resolveFeishuCredentials", () => {
|
||||
|
||||
expect(creds).toEqual({
|
||||
appId: "cli_123",
|
||||
appSecret: "secret_from_env",
|
||||
appSecret: "secret_from_env", // pragma: allowlist secret
|
||||
encryptKey: undefined,
|
||||
verificationToken: undefined,
|
||||
domain: "feishu",
|
||||
@@ -235,7 +235,7 @@ describe("resolveFeishuCredentials", () => {
|
||||
|
||||
expect(creds).toEqual({
|
||||
appId: "cli_123",
|
||||
appSecret: "secret_456",
|
||||
appSecret: "secret_456", // pragma: allowlist secret
|
||||
encryptKey: "enc",
|
||||
verificationToken: "vt",
|
||||
domain: "feishu",
|
||||
@@ -250,9 +250,9 @@ describe("resolveFeishuAccount", () => {
|
||||
feishu: {
|
||||
defaultAccount: "router-d",
|
||||
appId: "top_level_app",
|
||||
appSecret: "top_level_secret",
|
||||
appSecret: "top_level_secret", // pragma: allowlist secret
|
||||
accounts: {
|
||||
default: { appId: "cli_default", appSecret: "secret_default" },
|
||||
default: { appId: "cli_default", appSecret: "secret_default" }, // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -272,7 +272,7 @@ describe("resolveFeishuAccount", () => {
|
||||
defaultAccount: "router-d",
|
||||
accounts: {
|
||||
default: { enabled: true },
|
||||
"router-d": { appId: "cli_router", appSecret: "secret_router", enabled: true },
|
||||
"router-d": { appId: "cli_router", appSecret: "secret_router", enabled: true }, // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -291,8 +291,8 @@ describe("resolveFeishuAccount", () => {
|
||||
feishu: {
|
||||
defaultAccount: "router-d",
|
||||
accounts: {
|
||||
default: { appId: "cli_default", appSecret: "secret_default" },
|
||||
"router-d": { appId: "cli_router", appSecret: "secret_router" },
|
||||
default: { appId: "cli_default", appSecret: "secret_default" }, // pragma: allowlist secret
|
||||
"router-d": { appId: "cli_router", appSecret: "secret_router" }, // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -334,7 +334,7 @@ describe("resolveFeishuAccount", () => {
|
||||
main: {
|
||||
name: { bad: true },
|
||||
appId: "cli_123",
|
||||
appSecret: "secret_456",
|
||||
appSecret: "secret_456", // pragma: allowlist secret
|
||||
} as never,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1088,7 +1088,7 @@ describe("handleFeishuMessage command authorization", () => {
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "cli_test",
|
||||
appSecret: "sec_test",
|
||||
appSecret: "sec_test", // pragma: allowlist secret
|
||||
groups: {
|
||||
"oc-group": {
|
||||
requireMention: false,
|
||||
@@ -1151,7 +1151,7 @@ describe("handleFeishuMessage command authorization", () => {
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "cli_scope_bug",
|
||||
appSecret: "sec_scope_bug",
|
||||
appSecret: "sec_scope_bug", // pragma: allowlist secret
|
||||
groups: {
|
||||
"oc-group": {
|
||||
requireMention: false,
|
||||
|
||||
@@ -29,7 +29,7 @@ describe("registerFeishuChatTools", () => {
|
||||
feishu: {
|
||||
enabled: true,
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
appSecret: "app_secret", // pragma: allowlist secret
|
||||
tools: { chat: true },
|
||||
},
|
||||
},
|
||||
@@ -76,7 +76,7 @@ describe("registerFeishuChatTools", () => {
|
||||
feishu: {
|
||||
enabled: true,
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
appSecret: "app_secret", // pragma: allowlist secret
|
||||
tools: { chat: false },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -59,7 +59,7 @@ const baseAccount: ResolvedFeishuAccount = {
|
||||
enabled: true,
|
||||
configured: true,
|
||||
appId: "app_123",
|
||||
appSecret: "secret_123",
|
||||
appSecret: "secret_123", // pragma: allowlist secret
|
||||
domain: "feishu",
|
||||
config: {} as FeishuConfig,
|
||||
};
|
||||
@@ -120,7 +120,7 @@ describe("createFeishuClient HTTP timeout", () => {
|
||||
};
|
||||
|
||||
it("passes a custom httpInstance with default timeout to Lark.Client", () => {
|
||||
createFeishuClient({ appId: "app_1", appSecret: "secret_1", accountId: "timeout-test" });
|
||||
createFeishuClient({ appId: "app_1", appSecret: "secret_1", accountId: "timeout-test" }); // pragma: allowlist secret
|
||||
|
||||
const calls = (LarkClient as unknown as ReturnType<typeof vi.fn>).mock.calls;
|
||||
const lastCall = calls[calls.length - 1][0] as { httpInstance?: unknown };
|
||||
@@ -128,7 +128,7 @@ describe("createFeishuClient HTTP timeout", () => {
|
||||
});
|
||||
|
||||
it("injects default timeout into HTTP request options", async () => {
|
||||
createFeishuClient({ appId: "app_2", appSecret: "secret_2", accountId: "timeout-inject" });
|
||||
createFeishuClient({ appId: "app_2", appSecret: "secret_2", accountId: "timeout-inject" }); // pragma: allowlist secret
|
||||
|
||||
const calls = (LarkClient as unknown as ReturnType<typeof vi.fn>).mock.calls;
|
||||
const lastCall = calls[calls.length - 1][0] as {
|
||||
@@ -150,7 +150,7 @@ describe("createFeishuClient HTTP timeout", () => {
|
||||
});
|
||||
|
||||
it("allows explicit timeout override per-request", async () => {
|
||||
createFeishuClient({ appId: "app_3", appSecret: "secret_3", accountId: "timeout-override" });
|
||||
createFeishuClient({ appId: "app_3", appSecret: "secret_3", accountId: "timeout-override" }); // pragma: allowlist secret
|
||||
|
||||
const calls = (LarkClient as unknown as ReturnType<typeof vi.fn>).mock.calls;
|
||||
const lastCall = calls[calls.length - 1][0] as {
|
||||
@@ -169,7 +169,7 @@ describe("createFeishuClient HTTP timeout", () => {
|
||||
it("uses config-configured default timeout when provided", async () => {
|
||||
createFeishuClient({
|
||||
appId: "app_4",
|
||||
appSecret: "secret_4",
|
||||
appSecret: "secret_4", // pragma: allowlist secret
|
||||
accountId: "timeout-config",
|
||||
config: { httpTimeoutMs: 45_000 },
|
||||
});
|
||||
@@ -180,7 +180,7 @@ describe("createFeishuClient HTTP timeout", () => {
|
||||
it("falls back to default timeout when configured timeout is invalid", async () => {
|
||||
createFeishuClient({
|
||||
appId: "app_5",
|
||||
appSecret: "secret_5",
|
||||
appSecret: "secret_5", // pragma: allowlist secret
|
||||
accountId: "timeout-config-invalid",
|
||||
config: { httpTimeoutMs: -1 },
|
||||
});
|
||||
@@ -193,7 +193,7 @@ describe("createFeishuClient HTTP timeout", () => {
|
||||
|
||||
createFeishuClient({
|
||||
appId: "app_8",
|
||||
appSecret: "secret_8",
|
||||
appSecret: "secret_8", // pragma: allowlist secret
|
||||
accountId: "timeout-env-override",
|
||||
config: { httpTimeoutMs: 45_000 },
|
||||
});
|
||||
@@ -206,7 +206,7 @@ describe("createFeishuClient HTTP timeout", () => {
|
||||
|
||||
createFeishuClient({
|
||||
appId: "app_10",
|
||||
appSecret: "secret_10",
|
||||
appSecret: "secret_10", // pragma: allowlist secret
|
||||
accountId: "timeout-direct-override",
|
||||
httpTimeoutMs: 120_000,
|
||||
config: { httpTimeoutMs: 45_000 },
|
||||
@@ -220,7 +220,7 @@ describe("createFeishuClient HTTP timeout", () => {
|
||||
|
||||
createFeishuClient({
|
||||
appId: "app_9",
|
||||
appSecret: "secret_9",
|
||||
appSecret: "secret_9", // pragma: allowlist secret
|
||||
accountId: "timeout-env-clamp",
|
||||
});
|
||||
|
||||
@@ -230,13 +230,13 @@ describe("createFeishuClient HTTP timeout", () => {
|
||||
it("recreates cached client when configured timeout changes", async () => {
|
||||
createFeishuClient({
|
||||
appId: "app_6",
|
||||
appSecret: "secret_6",
|
||||
appSecret: "secret_6", // pragma: allowlist secret
|
||||
accountId: "timeout-cache-change",
|
||||
config: { httpTimeoutMs: 30_000 },
|
||||
});
|
||||
createFeishuClient({
|
||||
appId: "app_6",
|
||||
appSecret: "secret_6",
|
||||
appSecret: "secret_6", // pragma: allowlist secret
|
||||
accountId: "timeout-cache-change",
|
||||
config: { httpTimeoutMs: 45_000 },
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ describe("FeishuConfigSchema webhook validation", () => {
|
||||
const result = FeishuConfigSchema.safeParse({
|
||||
connectionMode: "webhook",
|
||||
appId: "cli_top",
|
||||
appSecret: "secret_top",
|
||||
appSecret: "secret_top", // pragma: allowlist secret
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
@@ -52,7 +52,7 @@ describe("FeishuConfigSchema webhook validation", () => {
|
||||
connectionMode: "webhook",
|
||||
verificationToken: "token_top",
|
||||
appId: "cli_top",
|
||||
appSecret: "secret_top",
|
||||
appSecret: "secret_top", // pragma: allowlist secret
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
@@ -64,7 +64,7 @@ describe("FeishuConfigSchema webhook validation", () => {
|
||||
main: {
|
||||
connectionMode: "webhook",
|
||||
appId: "cli_main",
|
||||
appSecret: "secret_main",
|
||||
appSecret: "secret_main", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -86,7 +86,7 @@ describe("FeishuConfigSchema webhook validation", () => {
|
||||
main: {
|
||||
connectionMode: "webhook",
|
||||
appId: "cli_main",
|
||||
appSecret: "secret_main",
|
||||
appSecret: "secret_main", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -171,7 +171,7 @@ describe("FeishuConfigSchema defaultAccount", () => {
|
||||
const result = FeishuConfigSchema.safeParse({
|
||||
defaultAccount: "router-d",
|
||||
accounts: {
|
||||
"router-d": { appId: "cli_router", appSecret: "secret_router" },
|
||||
"router-d": { appId: "cli_router", appSecret: "secret_router" }, // pragma: allowlist secret
|
||||
},
|
||||
});
|
||||
|
||||
@@ -182,7 +182,7 @@ describe("FeishuConfigSchema defaultAccount", () => {
|
||||
const result = FeishuConfigSchema.safeParse({
|
||||
defaultAccount: "router-d",
|
||||
accounts: {
|
||||
backup: { appId: "cli_backup", appSecret: "secret_backup" },
|
||||
backup: { appId: "cli_backup", appSecret: "secret_backup" }, // pragma: allowlist secret
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ describe("feishu_doc account selection", () => {
|
||||
feishu: {
|
||||
enabled: true,
|
||||
accounts: {
|
||||
a: { appId: "app-a", appSecret: "sec-a", tools: { doc: true } },
|
||||
b: { appId: "app-b", appSecret: "sec-b", tools: { doc: true } },
|
||||
a: { appId: "app-a", appSecret: "sec-a", tools: { doc: true } }, // pragma: allowlist secret
|
||||
b: { appId: "app-b", appSecret: "sec-b", tools: { doc: true } }, // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -73,7 +73,7 @@ function buildConfig(params: {
|
||||
[params.accountId]: {
|
||||
enabled: true,
|
||||
appId: "cli_test",
|
||||
appSecret: "secret_test",
|
||||
appSecret: "secret_test", // pragma: allowlist secret
|
||||
connectionMode: "webhook",
|
||||
webhookHost: "127.0.0.1",
|
||||
webhookPort: params.port,
|
||||
|
||||
@@ -34,7 +34,7 @@ describe("probeFeishu", () => {
|
||||
});
|
||||
|
||||
it("returns error when appId is missing", async () => {
|
||||
const result = await probeFeishu({ appSecret: "secret" } as never);
|
||||
const result = await probeFeishu({ appSecret: "secret" } as never); // pragma: allowlist secret
|
||||
expect(result).toEqual({ ok: false, error: "missing credentials (appId, appSecret)" });
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ describe("probeFeishu", () => {
|
||||
bot: { bot_name: "TestBot", open_id: "ou_abc123" },
|
||||
});
|
||||
|
||||
const result = await probeFeishu({ appId: "cli_123", appSecret: "secret" });
|
||||
const result = await probeFeishu({ appId: "cli_123", appSecret: "secret" }); // pragma: allowlist secret
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
appId: "cli_123",
|
||||
@@ -65,7 +65,7 @@ describe("probeFeishu", () => {
|
||||
bot: { bot_name: "TestBot", open_id: "ou_abc123" },
|
||||
});
|
||||
|
||||
await probeFeishu({ appId: "cli_123", appSecret: "secret" });
|
||||
await probeFeishu({ appId: "cli_123", appSecret: "secret" }); // pragma: allowlist secret
|
||||
|
||||
expect(requestFn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -98,7 +98,7 @@ describe("probeFeishu", () => {
|
||||
abortController.abort();
|
||||
|
||||
const result = await probeFeishu(
|
||||
{ appId: "cli_123", appSecret: "secret" },
|
||||
{ appId: "cli_123", appSecret: "secret" }, // pragma: allowlist secret
|
||||
{ abortSignal: abortController.signal },
|
||||
);
|
||||
|
||||
@@ -111,7 +111,7 @@ describe("probeFeishu", () => {
|
||||
bot: { bot_name: "TestBot", open_id: "ou_abc123" },
|
||||
});
|
||||
|
||||
const creds = { appId: "cli_123", appSecret: "secret" };
|
||||
const creds = { appId: "cli_123", appSecret: "secret" }; // pragma: allowlist secret
|
||||
const first = await probeFeishu(creds);
|
||||
const second = await probeFeishu(creds);
|
||||
|
||||
@@ -128,7 +128,7 @@ describe("probeFeishu", () => {
|
||||
bot: { bot_name: "TestBot", open_id: "ou_abc123" },
|
||||
});
|
||||
|
||||
const creds = { appId: "cli_123", appSecret: "secret" };
|
||||
const creds = { appId: "cli_123", appSecret: "secret" }; // pragma: allowlist secret
|
||||
await probeFeishu(creds);
|
||||
expect(requestFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -148,7 +148,7 @@ describe("probeFeishu", () => {
|
||||
const requestFn = makeRequestFn({ code: 99, msg: "token expired" });
|
||||
createFeishuClientMock.mockReturnValue({ request: requestFn });
|
||||
|
||||
const creds = { appId: "cli_123", appSecret: "secret" };
|
||||
const creds = { appId: "cli_123", appSecret: "secret" }; // pragma: allowlist secret
|
||||
const first = await probeFeishu(creds);
|
||||
const second = await probeFeishu(creds);
|
||||
expect(first).toMatchObject({ ok: false, error: "API error: token expired" });
|
||||
@@ -170,7 +170,7 @@ describe("probeFeishu", () => {
|
||||
const requestFn = vi.fn().mockRejectedValue(new Error("network error"));
|
||||
createFeishuClientMock.mockReturnValue({ request: requestFn });
|
||||
|
||||
const creds = { appId: "cli_123", appSecret: "secret" };
|
||||
const creds = { appId: "cli_123", appSecret: "secret" }; // pragma: allowlist secret
|
||||
const first = await probeFeishu(creds);
|
||||
const second = await probeFeishu(creds);
|
||||
expect(first).toMatchObject({ ok: false, error: "network error" });
|
||||
@@ -192,15 +192,15 @@ describe("probeFeishu", () => {
|
||||
bot: { bot_name: "Bot1", open_id: "ou_1" },
|
||||
});
|
||||
|
||||
await probeFeishu({ appId: "cli_aaa", appSecret: "s1" });
|
||||
await probeFeishu({ appId: "cli_aaa", appSecret: "s1" }); // pragma: allowlist secret
|
||||
expect(requestFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Different appId should trigger a new API call
|
||||
await probeFeishu({ appId: "cli_bbb", appSecret: "s2" });
|
||||
await probeFeishu({ appId: "cli_bbb", appSecret: "s2" }); // pragma: allowlist secret
|
||||
expect(requestFn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Same appId + appSecret as first call should return cached
|
||||
await probeFeishu({ appId: "cli_aaa", appSecret: "s1" });
|
||||
await probeFeishu({ appId: "cli_aaa", appSecret: "s1" }); // pragma: allowlist secret
|
||||
expect(requestFn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
@@ -211,12 +211,12 @@ describe("probeFeishu", () => {
|
||||
});
|
||||
|
||||
// First account with appId + secret A
|
||||
await probeFeishu({ appId: "cli_shared", appSecret: "secret_aaa" });
|
||||
await probeFeishu({ appId: "cli_shared", appSecret: "secret_aaa" }); // pragma: allowlist secret
|
||||
expect(requestFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Second account with same appId but different secret (e.g. after rotation)
|
||||
// must NOT reuse the cached result
|
||||
await probeFeishu({ appId: "cli_shared", appSecret: "secret_bbb" });
|
||||
await probeFeishu({ appId: "cli_shared", appSecret: "secret_bbb" }); // pragma: allowlist secret
|
||||
expect(requestFn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
@@ -227,14 +227,14 @@ describe("probeFeishu", () => {
|
||||
});
|
||||
|
||||
// Two accounts with same appId+appSecret but different accountIds are cached separately
|
||||
await probeFeishu({ accountId: "acct-1", appId: "cli_123", appSecret: "secret" });
|
||||
await probeFeishu({ accountId: "acct-1", appId: "cli_123", appSecret: "secret" }); // pragma: allowlist secret
|
||||
expect(requestFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
await probeFeishu({ accountId: "acct-2", appId: "cli_123", appSecret: "secret" });
|
||||
await probeFeishu({ accountId: "acct-2", appId: "cli_123", appSecret: "secret" }); // pragma: allowlist secret
|
||||
expect(requestFn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Same accountId should return cached
|
||||
await probeFeishu({ accountId: "acct-1", appId: "cli_123", appSecret: "secret" });
|
||||
await probeFeishu({ accountId: "acct-1", appId: "cli_123", appSecret: "secret" }); // pragma: allowlist secret
|
||||
expect(requestFn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
@@ -244,7 +244,7 @@ describe("probeFeishu", () => {
|
||||
bot: { bot_name: "TestBot", open_id: "ou_abc123" },
|
||||
});
|
||||
|
||||
const creds = { appId: "cli_123", appSecret: "secret" };
|
||||
const creds = { appId: "cli_123", appSecret: "secret" }; // pragma: allowlist secret
|
||||
await probeFeishu(creds);
|
||||
expect(requestFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -260,7 +260,7 @@ describe("probeFeishu", () => {
|
||||
data: { bot: { bot_name: "DataBot", open_id: "ou_data" } },
|
||||
});
|
||||
|
||||
const result = await probeFeishu({ appId: "cli_123", appSecret: "secret" });
|
||||
const result = await probeFeishu({ appId: "cli_123", appSecret: "secret" }); // pragma: allowlist secret
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
appId: "cli_123",
|
||||
|
||||
@@ -35,12 +35,12 @@ function createConfig(params: {
|
||||
accounts: {
|
||||
a: {
|
||||
appId: "app-a",
|
||||
appSecret: "sec-a",
|
||||
appSecret: "sec-a", // pragma: allowlist secret
|
||||
tools: params.toolsA,
|
||||
},
|
||||
b: {
|
||||
appId: "app-b",
|
||||
appSecret: "sec-b",
|
||||
appSecret: "sec-b", // pragma: allowlist secret
|
||||
tools: params.toolsB,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -308,7 +308,7 @@ describe("loginGeminiCliOAuth", () => {
|
||||
beforeEach(() => {
|
||||
envSnapshot = Object.fromEntries(ENV_KEYS.map((key) => [key, process.env[key]]));
|
||||
process.env.OPENCLAW_GEMINI_OAUTH_CLIENT_ID = "test-client-id.apps.googleusercontent.com";
|
||||
process.env.OPENCLAW_GEMINI_OAUTH_CLIENT_SECRET = "GOCSPX-test-client-secret";
|
||||
process.env.OPENCLAW_GEMINI_OAUTH_CLIENT_SECRET = "GOCSPX-test-client-secret"; // pragma: allowlist secret
|
||||
delete process.env.GEMINI_CLI_OAUTH_CLIENT_ID;
|
||||
delete process.env.GEMINI_CLI_OAUTH_CLIENT_SECRET;
|
||||
delete process.env.GOOGLE_CLOUD_PROJECT;
|
||||
|
||||
@@ -81,7 +81,7 @@ describe("sendGoogleChatMessage", () => {
|
||||
});
|
||||
|
||||
const [url, init] = fetchMock.mock.calls[0] ?? [];
|
||||
expect(String(url)).toContain("messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD");
|
||||
expect(String(url)).toContain("messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD"); // pragma: allowlist secret
|
||||
expect(JSON.parse(String(init?.body))).toMatchObject({
|
||||
text: "hello",
|
||||
thread: { name: "spaces/AAA/threads/xyz" },
|
||||
|
||||
@@ -74,12 +74,12 @@ describe("looksLikeMattermostTargetId", () => {
|
||||
it("recognizes 26-char alphanumeric Mattermost IDs", () => {
|
||||
expect(looksLikeMattermostTargetId("abcdefghijklmnopqrstuvwxyz")).toBe(true);
|
||||
expect(looksLikeMattermostTargetId("12345678901234567890123456")).toBe(true);
|
||||
expect(looksLikeMattermostTargetId("AbCdEf1234567890abcdef1234")).toBe(true);
|
||||
expect(looksLikeMattermostTargetId("AbCdEf1234567890abcdef1234")).toBe(true); // pragma: allowlist secret
|
||||
});
|
||||
|
||||
it("recognizes DM channel format (26__26)", () => {
|
||||
expect(
|
||||
looksLikeMattermostTargetId("abcdefghijklmnopqrstuvwxyz__12345678901234567890123456"),
|
||||
looksLikeMattermostTargetId("abcdefghijklmnopqrstuvwxyz__12345678901234567890123456"), // pragma: allowlist secret
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@@ -91,6 +91,6 @@ describe("looksLikeMattermostTargetId", () => {
|
||||
});
|
||||
|
||||
it("rejects strings longer than 26 chars that are not DM format", () => {
|
||||
expect(looksLikeMattermostTargetId("abcdefghijklmnopqrstuvwxyz1")).toBe(false);
|
||||
expect(looksLikeMattermostTargetId("abcdefghijklmnopqrstuvwxyz1")).toBe(false); // pragma: allowlist secret
|
||||
});
|
||||
});
|
||||
|
||||
@@ -140,7 +140,7 @@ function createConfig(port: number): OpenClawConfig {
|
||||
msteams: {
|
||||
enabled: true,
|
||||
appId: "app-id",
|
||||
appPassword: "app-password",
|
||||
appPassword: "app-password", // pragma: allowlist secret
|
||||
tenantId: "tenant-id",
|
||||
webhook: {
|
||||
port,
|
||||
|
||||
@@ -35,7 +35,7 @@ describe("resolveMSTeamsCredentials", () => {
|
||||
|
||||
expect(resolved).toEqual({
|
||||
appId: "app-id",
|
||||
appPassword: "app-password",
|
||||
appPassword: "app-password", // pragma: allowlist secret
|
||||
tenantId: "tenant-id",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,11 +21,11 @@ function buildAccount(): ResolvedNextcloudTalkAccount {
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
baseUrl: "https://nextcloud.example.com",
|
||||
secret: "secret",
|
||||
secretSource: "config",
|
||||
secret: "secret", // pragma: allowlist secret
|
||||
secretSource: "config", // pragma: allowlist secret
|
||||
config: {
|
||||
baseUrl: "https://nextcloud.example.com",
|
||||
botSecret: "secret",
|
||||
botSecret: "secret", // pragma: allowlist secret
|
||||
webhookPath: "/nextcloud-talk-webhook",
|
||||
webhookPort: 8788,
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ const hoisted = vi.hoisted(() => ({
|
||||
resolveNextcloudTalkAccount: vi.fn(() => ({
|
||||
accountId: "default",
|
||||
baseUrl: "https://nextcloud.example.com",
|
||||
secret: "secret-value",
|
||||
secret: "secret-value", // pragma: allowlist secret
|
||||
})),
|
||||
generateNextcloudTalkSignature: vi.fn(() => ({
|
||||
random: "r",
|
||||
|
||||
@@ -51,8 +51,8 @@ describe("nostr outbound cfg threading", () => {
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
publicKey: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
|
||||
privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", // pragma: allowlist secret
|
||||
publicKey: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", // pragma: allowlist secret
|
||||
relays: ["wss://relay.example.com"],
|
||||
config: {},
|
||||
},
|
||||
@@ -63,7 +63,7 @@ describe("nostr outbound cfg threading", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
nostr: {
|
||||
privateKey: "resolved-nostr-private-key",
|
||||
privateKey: "resolved-nostr-private-key", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -144,7 +144,7 @@ describe("slackPlugin config", () => {
|
||||
slack: {
|
||||
mode: "http",
|
||||
botToken: "xoxb-http",
|
||||
signingSecret: "secret-http",
|
||||
signingSecret: "secret-http", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -214,9 +214,9 @@ describe("slackPlugin config", () => {
|
||||
configured: true,
|
||||
mode: "http",
|
||||
botTokenStatus: "available",
|
||||
signingSecretStatus: "configured_unavailable",
|
||||
signingSecretStatus: "configured_unavailable", // pragma: allowlist secret
|
||||
botTokenSource: "config",
|
||||
signingSecretSource: "config",
|
||||
signingSecretSource: "config", // pragma: allowlist secret
|
||||
config: {
|
||||
mode: "http",
|
||||
botToken: "xoxb-http",
|
||||
|
||||
@@ -129,7 +129,7 @@ describe("telegramPlugin duplicate token guard", () => {
|
||||
cfg.channels!.telegram!.accounts!.ops = {
|
||||
...cfg.channels!.telegram!.accounts!.ops,
|
||||
webhookUrl: "https://example.test/telegram-webhook",
|
||||
webhookSecret: "secret",
|
||||
webhookSecret: "secret", // pragma: allowlist secret
|
||||
webhookPort: 9876,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user