mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-09 15:35:17 +00:00
fix(logging): redact phone numbers and message content from WhatsApp logs
Apply redactIdentifier() (SHA-256 hashing) to all recipient JIDs and phone numbers logged by sendMessageWhatsApp, sendReactionWhatsApp, sendPollWhatsApp, and runWebHeartbeatOnce. Remove poll question text and message preview content from log entries, replacing with character counts where useful for debugging. The existing redactIdentifier() utility in src/logging/redact-identifier.ts was already implemented but not wired into any WhatsApp logging path. This commit connects it to all affected call sites while leaving functional parameters (actual send calls, event emitters) untouched. Closes #24957
This commit is contained in:
committed by
Peter Steinberger
parent
0bdcca2f35
commit
aef45b2abb
@@ -18,13 +18,13 @@ import {
|
||||
import { emitHeartbeatEvent, resolveIndicatorType } from "../../infra/heartbeat-events.js";
|
||||
import { resolveHeartbeatVisibility } from "../../infra/heartbeat-visibility.js";
|
||||
import { getChildLogger } from "../../logging.js";
|
||||
import { redactIdentifier } from "../../logging/redact-identifier.js";
|
||||
import { normalizeMainKey } from "../../routing/session-key.js";
|
||||
import { sendMessageWhatsApp } from "../outbound.js";
|
||||
import { newConnectionId } from "../reconnect.js";
|
||||
import { formatError } from "../session.js";
|
||||
import { whatsappHeartbeatLog } from "./loggers.js";
|
||||
import { getSessionSnapshot } from "./session-snapshot.js";
|
||||
import { elide } from "./util.js";
|
||||
|
||||
export async function runWebHeartbeatOnce(opts: {
|
||||
cfg?: ReturnType<typeof loadConfig>;
|
||||
@@ -40,10 +40,11 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
const replyResolver = opts.replyResolver ?? getReplyFromConfig;
|
||||
const sender = opts.sender ?? sendMessageWhatsApp;
|
||||
const runId = newConnectionId();
|
||||
const redactedTo = redactIdentifier(to);
|
||||
const heartbeatLogger = getChildLogger({
|
||||
module: "web-heartbeat",
|
||||
runId,
|
||||
to,
|
||||
to: redactedTo,
|
||||
});
|
||||
|
||||
const cfg = cfgOverride ?? loadConfig();
|
||||
@@ -57,20 +58,20 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
return false;
|
||||
}
|
||||
if (dryRun) {
|
||||
whatsappHeartbeatLog.info(`[dry-run] heartbeat ok -> ${to}`);
|
||||
whatsappHeartbeatLog.info(`[dry-run] heartbeat ok -> ${redactedTo}`);
|
||||
return false;
|
||||
}
|
||||
const sendResult = await sender(to, heartbeatOkText, { verbose });
|
||||
heartbeatLogger.info(
|
||||
{
|
||||
to,
|
||||
to: redactedTo,
|
||||
messageId: sendResult.messageId,
|
||||
chars: heartbeatOkText.length,
|
||||
reason: "heartbeat-ok",
|
||||
},
|
||||
"heartbeat ok sent",
|
||||
);
|
||||
whatsappHeartbeatLog.info(`heartbeat ok sent to ${to} (id ${sendResult.messageId})`);
|
||||
whatsappHeartbeatLog.info(`heartbeat ok sent to ${redactedTo} (id ${sendResult.messageId})`);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -100,7 +101,7 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
if (verbose) {
|
||||
heartbeatLogger.info(
|
||||
{
|
||||
to,
|
||||
to: redactedTo,
|
||||
sessionKey: sessionSnapshot.key,
|
||||
sessionId: sessionId ?? sessionSnapshot.entry?.sessionId ?? null,
|
||||
sessionFresh: sessionSnapshot.fresh,
|
||||
@@ -122,7 +123,7 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
if (overrideBody) {
|
||||
if (dryRun) {
|
||||
whatsappHeartbeatLog.info(
|
||||
`[dry-run] web send -> ${to}: ${elide(overrideBody.trim(), 200)} (manual message)`,
|
||||
`[dry-run] web send -> ${redactedTo} (${overrideBody.trim().length} chars, manual message)`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -137,19 +138,21 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
});
|
||||
heartbeatLogger.info(
|
||||
{
|
||||
to,
|
||||
to: redactedTo,
|
||||
messageId: sendResult.messageId,
|
||||
chars: overrideBody.length,
|
||||
reason: "manual-message",
|
||||
},
|
||||
"manual heartbeat message sent",
|
||||
);
|
||||
whatsappHeartbeatLog.info(`manual heartbeat sent to ${to} (id ${sendResult.messageId})`);
|
||||
whatsappHeartbeatLog.info(
|
||||
`manual heartbeat sent to ${redactedTo} (id ${sendResult.messageId})`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!visibility.showAlerts && !visibility.showOk && !visibility.useIndicator) {
|
||||
heartbeatLogger.info({ to, reason: "alerts-disabled" }, "heartbeat skipped");
|
||||
heartbeatLogger.info({ to: redactedTo, reason: "alerts-disabled" }, "heartbeat skipped");
|
||||
emitHeartbeatEvent({
|
||||
status: "skipped",
|
||||
to,
|
||||
@@ -181,7 +184,7 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
) {
|
||||
heartbeatLogger.info(
|
||||
{
|
||||
to,
|
||||
to: redactedTo,
|
||||
reason: "empty-reply",
|
||||
sessionId: sessionSnapshot.entry?.sessionId ?? null,
|
||||
},
|
||||
@@ -226,7 +229,7 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
}
|
||||
|
||||
heartbeatLogger.info(
|
||||
{ to, reason: "heartbeat-token", rawLength: replyPayload.text?.length },
|
||||
{ to: redactedTo, reason: "heartbeat-token", rawLength: replyPayload.text?.length },
|
||||
"heartbeat skipped",
|
||||
);
|
||||
const okSent = await maybeSendHeartbeatOk();
|
||||
@@ -241,14 +244,17 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
}
|
||||
|
||||
if (hasMedia) {
|
||||
heartbeatLogger.warn({ to }, "heartbeat reply contained media; sending text only");
|
||||
heartbeatLogger.warn(
|
||||
{ to: redactedTo },
|
||||
"heartbeat reply contained media; sending text only",
|
||||
);
|
||||
}
|
||||
|
||||
const finalText = stripped.text || replyPayload.text || "";
|
||||
|
||||
// Check if alerts are disabled for WhatsApp
|
||||
if (!visibility.showAlerts) {
|
||||
heartbeatLogger.info({ to, reason: "alerts-disabled" }, "heartbeat skipped");
|
||||
heartbeatLogger.info({ to: redactedTo, reason: "alerts-disabled" }, "heartbeat skipped");
|
||||
emitHeartbeatEvent({
|
||||
status: "skipped",
|
||||
to,
|
||||
@@ -262,8 +268,11 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
heartbeatLogger.info({ to, reason: "dry-run", chars: finalText.length }, "heartbeat dry-run");
|
||||
whatsappHeartbeatLog.info(`[dry-run] heartbeat -> ${to}: ${elide(finalText, 200)}`);
|
||||
heartbeatLogger.info(
|
||||
{ to: redactedTo, reason: "dry-run", chars: finalText.length },
|
||||
"heartbeat dry-run",
|
||||
);
|
||||
whatsappHeartbeatLog.info(`[dry-run] heartbeat -> ${redactedTo} (${finalText.length} chars)`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -278,17 +287,16 @@ export async function runWebHeartbeatOnce(opts: {
|
||||
});
|
||||
heartbeatLogger.info(
|
||||
{
|
||||
to,
|
||||
to: redactedTo,
|
||||
messageId: sendResult.messageId,
|
||||
chars: finalText.length,
|
||||
preview: elide(finalText, 140),
|
||||
},
|
||||
"heartbeat sent",
|
||||
);
|
||||
whatsappHeartbeatLog.info(`heartbeat alert sent to ${to}`);
|
||||
whatsappHeartbeatLog.info(`heartbeat alert sent to ${redactedTo}`);
|
||||
} catch (err) {
|
||||
const reason = formatError(err);
|
||||
heartbeatLogger.warn({ to, error: reason }, "heartbeat failed");
|
||||
heartbeatLogger.warn({ to: redactedTo, error: reason }, "heartbeat failed");
|
||||
whatsappHeartbeatLog.warn(`heartbeat failed (${reason})`);
|
||||
emitHeartbeatEvent({
|
||||
status: "failed",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { loadConfig } from "../config/config.js";
|
||||
import { resolveMarkdownTableMode } from "../config/markdown-tables.js";
|
||||
import { generateSecureUuid } from "../infra/secure-random.js";
|
||||
import { getChildLogger } from "../logging/logger.js";
|
||||
import { redactIdentifier } from "../logging/redact-identifier.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { convertMarkdownTables } from "../markdown/tables.js";
|
||||
import { markdownToWhatsApp } from "../markdown/whatsapp.js";
|
||||
@@ -37,13 +38,15 @@ export async function sendMessageWhatsApp(
|
||||
});
|
||||
text = convertMarkdownTables(text ?? "", tableMode);
|
||||
text = markdownToWhatsApp(text);
|
||||
const redactedTo = redactIdentifier(to);
|
||||
const logger = getChildLogger({
|
||||
module: "web-outbound",
|
||||
correlationId,
|
||||
to,
|
||||
to: redactedTo,
|
||||
});
|
||||
try {
|
||||
const jid = toWhatsappJid(to);
|
||||
const redactedJid = redactIdentifier(jid);
|
||||
let mediaBuffer: Buffer | undefined;
|
||||
let mediaType: string | undefined;
|
||||
let documentFileName: string | undefined;
|
||||
@@ -69,8 +72,8 @@ export async function sendMessageWhatsApp(
|
||||
documentFileName = media.fileName;
|
||||
}
|
||||
}
|
||||
outboundLog.info(`Sending message -> ${jid}${options.mediaUrl ? " (media)" : ""}`);
|
||||
logger.info({ jid, hasMedia: Boolean(options.mediaUrl) }, "sending message");
|
||||
outboundLog.info(`Sending message -> ${redactedJid}${options.mediaUrl ? " (media)" : ""}`);
|
||||
logger.info({ jid: redactedJid, hasMedia: Boolean(options.mediaUrl) }, "sending message");
|
||||
await active.sendComposingTo(to);
|
||||
const hasExplicitAccountId = Boolean(options.accountId?.trim());
|
||||
const accountId = hasExplicitAccountId ? resolvedAccountId : undefined;
|
||||
@@ -88,13 +91,13 @@ export async function sendMessageWhatsApp(
|
||||
const messageId = (result as { messageId?: string })?.messageId ?? "unknown";
|
||||
const durationMs = Date.now() - startedAt;
|
||||
outboundLog.info(
|
||||
`Sent message ${messageId} -> ${jid}${options.mediaUrl ? " (media)" : ""} (${durationMs}ms)`,
|
||||
`Sent message ${messageId} -> ${redactedJid}${options.mediaUrl ? " (media)" : ""} (${durationMs}ms)`,
|
||||
);
|
||||
logger.info({ jid, messageId }, "sent message");
|
||||
logger.info({ jid: redactedJid, messageId }, "sent message");
|
||||
return { messageId, toJid: jid };
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
{ err: String(err), to, hasMedia: Boolean(options.mediaUrl) },
|
||||
{ err: String(err), to: redactedTo, hasMedia: Boolean(options.mediaUrl) },
|
||||
"failed to send via web session",
|
||||
);
|
||||
throw err;
|
||||
@@ -114,16 +117,18 @@ export async function sendReactionWhatsApp(
|
||||
): Promise<void> {
|
||||
const correlationId = generateSecureUuid();
|
||||
const { listener: active } = requireActiveWebListener(options.accountId);
|
||||
const redactedChatJid = redactIdentifier(chatJid);
|
||||
const logger = getChildLogger({
|
||||
module: "web-outbound",
|
||||
correlationId,
|
||||
chatJid,
|
||||
chatJid: redactedChatJid,
|
||||
messageId,
|
||||
});
|
||||
try {
|
||||
const jid = toWhatsappJid(chatJid);
|
||||
const redactedJid = redactIdentifier(jid);
|
||||
outboundLog.info(`Sending reaction "${emoji}" -> message ${messageId}`);
|
||||
logger.info({ chatJid: jid, messageId, emoji }, "sending reaction");
|
||||
logger.info({ chatJid: redactedJid, messageId, emoji }, "sending reaction");
|
||||
await active.sendReaction(
|
||||
chatJid,
|
||||
messageId,
|
||||
@@ -132,10 +137,10 @@ export async function sendReactionWhatsApp(
|
||||
options.participant,
|
||||
);
|
||||
outboundLog.info(`Sent reaction "${emoji}" -> message ${messageId}`);
|
||||
logger.info({ chatJid: jid, messageId, emoji }, "sent reaction");
|
||||
logger.info({ chatJid: redactedJid, messageId, emoji }, "sent reaction");
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
{ err: String(err), chatJid, messageId, emoji },
|
||||
{ err: String(err), chatJid: redactedChatJid, messageId, emoji },
|
||||
"failed to send reaction via web session",
|
||||
);
|
||||
throw err;
|
||||
@@ -150,19 +155,20 @@ export async function sendPollWhatsApp(
|
||||
const correlationId = generateSecureUuid();
|
||||
const startedAt = Date.now();
|
||||
const { listener: active } = requireActiveWebListener(options.accountId);
|
||||
const redactedTo = redactIdentifier(to);
|
||||
const logger = getChildLogger({
|
||||
module: "web-outbound",
|
||||
correlationId,
|
||||
to,
|
||||
to: redactedTo,
|
||||
});
|
||||
try {
|
||||
const jid = toWhatsappJid(to);
|
||||
const redactedJid = redactIdentifier(jid);
|
||||
const normalized = normalizePollInput(poll, { maxOptions: 12 });
|
||||
outboundLog.info(`Sending poll -> ${jid}: "${normalized.question}"`);
|
||||
outboundLog.info(`Sending poll -> ${redactedJid}`);
|
||||
logger.info(
|
||||
{
|
||||
jid,
|
||||
question: normalized.question,
|
||||
jid: redactedJid,
|
||||
optionCount: normalized.options.length,
|
||||
maxSelections: normalized.maxSelections,
|
||||
},
|
||||
@@ -171,14 +177,11 @@ export async function sendPollWhatsApp(
|
||||
const result = await active.sendPoll(to, normalized);
|
||||
const messageId = (result as { messageId?: string })?.messageId ?? "unknown";
|
||||
const durationMs = Date.now() - startedAt;
|
||||
outboundLog.info(`Sent poll ${messageId} -> ${jid} (${durationMs}ms)`);
|
||||
logger.info({ jid, messageId }, "sent poll");
|
||||
outboundLog.info(`Sent poll ${messageId} -> ${redactedJid} (${durationMs}ms)`);
|
||||
logger.info({ jid: redactedJid, messageId }, "sent poll");
|
||||
return { messageId, toJid: jid };
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
{ err: String(err), to, question: poll.question },
|
||||
"failed to send poll via web session",
|
||||
);
|
||||
logger.error({ err: String(err), to: redactedTo }, "failed to send poll via web session");
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user