fix(auto-reply): prevent sender spoofing in group prompts

This commit is contained in:
Peter Steinberger
2026-02-10 00:35:56 -06:00
parent 8ff1618bfc
commit 53273b490b
42 changed files with 405 additions and 243 deletions

View File

@@ -12,7 +12,6 @@ import {
resetInboundDedupe,
shouldSkipDuplicateInbound,
} from "./reply/inbound-dedupe.js";
import { formatInboundBodyWithSenderMeta } from "./reply/inbound-sender-meta.js";
import { normalizeInboundTextNewlines } from "./reply/inbound-text.js";
import {
buildMentionRegexes,
@@ -80,7 +79,8 @@ describe("finalizeInboundContext", () => {
const out = finalizeInboundContext(ctx);
expect(out.Body).toBe("a\nb\nc");
expect(out.RawBody).toBe("raw\nline");
expect(out.BodyForAgent).toBe("a\nb\nc");
// Prefer clean text over legacy envelope-shaped Body when RawBody is present.
expect(out.BodyForAgent).toBe("raw\nline");
expect(out.BodyForCommands).toBe("raw\nline");
expect(out.CommandAuthorized).toBe(false);
expect(out.ChatType).toBe("channel");
@@ -101,58 +101,6 @@ describe("finalizeInboundContext", () => {
});
});
describe("formatInboundBodyWithSenderMeta", () => {
it("does nothing for direct messages", () => {
const ctx: MsgContext = { ChatType: "direct", SenderName: "Alice", SenderId: "A1" };
expect(formatInboundBodyWithSenderMeta({ ctx, body: "[X] hi" })).toBe("[X] hi");
});
it("appends a sender meta line for non-direct messages", () => {
const ctx: MsgContext = { ChatType: "group", SenderName: "Alice", SenderId: "A1" };
expect(formatInboundBodyWithSenderMeta({ ctx, body: "[X] hi" })).toBe(
"[X] hi\n[from: Alice (A1)]",
);
});
it("prefers SenderE164 in the label when present", () => {
const ctx: MsgContext = {
ChatType: "group",
SenderName: "Bob",
SenderId: "bob@s.whatsapp.net",
SenderE164: "+222",
};
expect(formatInboundBodyWithSenderMeta({ ctx, body: "[X] hi" })).toBe(
"[X] hi\n[from: Bob (+222)]",
);
});
it("appends with a real newline even if the body contains literal \\n", () => {
const ctx: MsgContext = { ChatType: "group", SenderName: "Bob", SenderId: "+222" };
expect(formatInboundBodyWithSenderMeta({ ctx, body: "[X] one\\n[X] two" })).toBe(
"[X] one\\n[X] two\n[from: Bob (+222)]",
);
});
it("does not duplicate a sender meta line when one is already present", () => {
const ctx: MsgContext = { ChatType: "group", SenderName: "Alice", SenderId: "A1" };
expect(formatInboundBodyWithSenderMeta({ ctx, body: "[X] hi\n[from: Alice (A1)]" })).toBe(
"[X] hi\n[from: Alice (A1)]",
);
});
it("does not append when the body already includes a sender prefix", () => {
const ctx: MsgContext = { ChatType: "group", SenderName: "Alice", SenderId: "A1" };
expect(formatInboundBodyWithSenderMeta({ ctx, body: "Alice (A1): hi" })).toBe("Alice (A1): hi");
});
it("does not append when the sender prefix follows an envelope header", () => {
const ctx: MsgContext = { ChatType: "group", SenderName: "Alice", SenderId: "A1" };
expect(formatInboundBodyWithSenderMeta({ ctx, body: "[Signal Group] Alice (A1): hi" })).toBe(
"[Signal Group] Alice (A1): hi",
);
});
});
describe("inbound dedupe", () => {
it("builds a stable key when MessageSid is present", () => {
const ctx: MsgContext = {
@@ -256,8 +204,8 @@ describe("createInboundDebouncer", () => {
});
});
describe("initSessionState sender meta", () => {
it("injects sender meta into BodyStripped for group chats", async () => {
describe("initSessionState BodyStripped", () => {
it("prefers BodyForAgent over Body for group chats", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sender-meta-"));
const storePath = path.join(root, "sessions.json");
const cfg = { session: { store: storePath } } as OpenClawConfig;
@@ -265,6 +213,7 @@ describe("initSessionState sender meta", () => {
const result = await initSessionState({
ctx: {
Body: "[WhatsApp 123@g.us] ping",
BodyForAgent: "ping",
ChatType: "group",
SenderName: "Bob",
SenderE164: "+222",
@@ -275,10 +224,10 @@ describe("initSessionState sender meta", () => {
commandAuthorized: true,
});
expect(result.sessionCtx.BodyStripped).toBe("[WhatsApp 123@g.us] ping\n[from: Bob (+222)]");
expect(result.sessionCtx.BodyStripped).toBe("ping");
});
it("does not inject sender meta for direct chats", async () => {
it("prefers BodyForAgent over Body for direct chats", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sender-meta-direct-"));
const storePath = path.join(root, "sessions.json");
const cfg = { session: { store: storePath } } as OpenClawConfig;
@@ -286,6 +235,7 @@ describe("initSessionState sender meta", () => {
const result = await initSessionState({
ctx: {
Body: "[WhatsApp +1] ping",
BodyForAgent: "ping",
ChatType: "direct",
SenderName: "Bob",
SenderE164: "+222",
@@ -295,7 +245,7 @@ describe("initSessionState sender meta", () => {
commandAuthorized: true,
});
expect(result.sessionCtx.BodyStripped).toBe("[WhatsApp +1] ping");
expect(result.sessionCtx.BodyStripped).toBe("ping");
});
});