test(whatsapp): add log redaction coverage

This commit is contained in:
Peter Steinberger
2026-02-24 03:34:31 +00:00
parent d95ee859f8
commit 0bdcca2f35
3 changed files with 63 additions and 4 deletions

View File

@@ -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.

View File

@@ -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"');
});
});

View File

@@ -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,