mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-25 15:37:32 +00:00
test(whatsapp): add log redaction coverage
This commit is contained in:
@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- WhatsApp/Logging: redact outbound recipient identifiers in WhatsApp outbound + heartbeat logs and remove message/poll preview text from those log lines. (#24980) Thanks @coygeek.
|
||||
- Telegram/Media SSRF: keep RFC2544 benchmark range (`198.18.0.0/15`) blocked by default, add an explicit SSRF-policy opt-in for Telegram media downloads, and keep other channels/URL fetch paths blocked. (#24982) Thanks @stakeswky.
|
||||
- Security/Shell env fallback: remove trusted-prefix shell-path fallback and only trust login shells explicitly registered in `/etc/shells`, defaulting to `/bin/sh` when `SHELL` is not registered. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/Exec approvals: bind `host=node` approvals to explicit `nodeId`, reject cross-node replay of approved `system.run` requests, and include the target node in approval prompts. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { getReplyFromConfig } from "../../auto-reply/reply.js";
|
||||
import { HEARTBEAT_TOKEN } from "../../auto-reply/tokens.js";
|
||||
import { redactIdentifier } from "../../logging/redact-identifier.js";
|
||||
import type { sendMessageWhatsApp } from "../outbound.js";
|
||||
|
||||
const state = vi.hoisted(() => ({
|
||||
@@ -15,6 +16,10 @@ const state = vi.hoisted(() => ({
|
||||
idleExpiresAt: null as number | null,
|
||||
},
|
||||
events: [] as unknown[],
|
||||
loggerInfoCalls: [] as unknown[][],
|
||||
loggerWarnCalls: [] as unknown[][],
|
||||
heartbeatInfoLogs: [] as string[],
|
||||
heartbeatWarnLogs: [] as string[],
|
||||
}));
|
||||
|
||||
vi.mock("../../agents/current-time.js", () => ({
|
||||
@@ -64,15 +69,15 @@ vi.mock("../../infra/heartbeat-events.js", () => ({
|
||||
|
||||
vi.mock("../../logging.js", () => ({
|
||||
getChildLogger: () => ({
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
info: (...args: unknown[]) => state.loggerInfoCalls.push(args),
|
||||
warn: (...args: unknown[]) => state.loggerWarnCalls.push(args),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("./loggers.js", () => ({
|
||||
whatsappHeartbeatLog: {
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
info: (msg: string) => state.heartbeatInfoLogs.push(msg),
|
||||
warn: (msg: string) => state.heartbeatWarnLogs.push(msg),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -115,6 +120,10 @@ describe("runWebHeartbeatOnce", () => {
|
||||
idleExpiresAt: null,
|
||||
};
|
||||
state.events = [];
|
||||
state.loggerInfoCalls = [];
|
||||
state.loggerWarnCalls = [];
|
||||
state.heartbeatInfoLogs = [];
|
||||
state.heartbeatWarnLogs = [];
|
||||
|
||||
senderMock = vi.fn(async () => ({ messageId: "m1" }));
|
||||
sender = senderMock as unknown as typeof sendMessageWhatsApp;
|
||||
@@ -187,4 +196,23 @@ describe("runWebHeartbeatOnce", () => {
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("redacts recipient and omits body preview in heartbeat logs", async () => {
|
||||
replyResolverMock.mockResolvedValue({ text: "sensitive heartbeat body" });
|
||||
const { runWebHeartbeatOnce } = await getModules();
|
||||
await runWebHeartbeatOnce(buildRunArgs({ dryRun: true }));
|
||||
|
||||
const expected = redactIdentifier("+123");
|
||||
const heartbeatLogs = state.heartbeatInfoLogs.join("\n");
|
||||
const childLoggerLogs = state.loggerInfoCalls.map((entry) => JSON.stringify(entry)).join("\n");
|
||||
|
||||
expect(heartbeatLogs).toContain(expected);
|
||||
expect(heartbeatLogs).not.toContain("+123");
|
||||
expect(heartbeatLogs).not.toContain("sensitive heartbeat body");
|
||||
|
||||
expect(childLoggerLogs).toContain(expected);
|
||||
expect(childLoggerLogs).not.toContain("+123");
|
||||
expect(childLoggerLogs).not.toContain("sensitive heartbeat body");
|
||||
expect(childLoggerLogs).not.toContain('"preview"');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import crypto from "node:crypto";
|
||||
import fsSync from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resetLogger, setLoggerOverride } from "../logging.js";
|
||||
import { redactIdentifier } from "../logging/redact-identifier.js";
|
||||
import { setActiveWebListener } from "./active-listener.js";
|
||||
|
||||
const loadWebMediaMock = vi.fn();
|
||||
@@ -154,6 +159,31 @@ describe("web outbound", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("redacts recipients and poll text in outbound logs", async () => {
|
||||
const logPath = path.join(os.tmpdir(), `openclaw-outbound-${crypto.randomUUID()}.log`);
|
||||
setLoggerOverride({ level: "trace", file: logPath });
|
||||
|
||||
await sendPollWhatsApp(
|
||||
"+1555",
|
||||
{ question: "Lunch?", options: ["Pizza", "Sushi"], maxSelections: 1 },
|
||||
{ verbose: false },
|
||||
);
|
||||
|
||||
await vi.waitFor(
|
||||
() => {
|
||||
expect(fsSync.existsSync(logPath)).toBe(true);
|
||||
},
|
||||
{ timeout: 2_000, interval: 5 },
|
||||
);
|
||||
|
||||
const content = fsSync.readFileSync(logPath, "utf-8");
|
||||
expect(content).toContain(redactIdentifier("+1555"));
|
||||
expect(content).toContain(redactIdentifier("1555@s.whatsapp.net"));
|
||||
expect(content).not.toContain(`"to":"+1555"`);
|
||||
expect(content).not.toContain(`"jid":"1555@s.whatsapp.net"`);
|
||||
expect(content).not.toContain("Lunch?");
|
||||
});
|
||||
|
||||
it("sends reactions via active listener", async () => {
|
||||
await sendReactionWhatsApp("1555@s.whatsapp.net", "msg123", "✅", {
|
||||
verbose: false,
|
||||
|
||||
Reference in New Issue
Block a user