Skip to content

Commit a4e02cd

Browse files
authored
fix(elevated): reject group ids as senders (#91748)
* fix(elevated): reject group ids as senders * fix(elevated): keep channel parsing out of core
1 parent b6a3f29 commit a4e02cd

4 files changed

Lines changed: 64 additions & 28 deletions

File tree

src/auto-reply/command-auth.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/** Command authorization helpers for owner and allowlist checks. */
22
import {
3-
normalizeLowercaseStringOrEmpty,
43
normalizeOptionalLowercaseString,
54
normalizeOptionalString,
65
} from "@openclaw/normalization-core/string-coerce";
@@ -19,6 +18,7 @@ import {
1918
normalizeMessageChannel,
2019
} from "../utils/message-channel.js";
2120
import { isNativeCommandTurn, resolveCommandTurnContext } from "./command-turn-context.js";
21+
import { shouldUseFromAsSenderFallback } from "./sender-identity.js";
2222
import type { MsgContext } from "./templating.js";
2323

2424
export type CommandAuthorization = {
@@ -461,32 +461,6 @@ function resolveCommandSenderAuthorization(params: {
461461
return params.commandAuthorized && (params.isOwnerForCommands || params.nativeCommandAuthorized);
462462
}
463463

464-
function isConversationLikeIdentity(value: string): boolean {
465-
const normalized = normalizeOptionalLowercaseString(value);
466-
if (!normalized) {
467-
return false;
468-
}
469-
if (normalized.startsWith("chat_id:")) {
470-
return true;
471-
}
472-
return /(^|:)(channel|group|thread|topic|room|space|spaces):/.test(normalized);
473-
}
474-
475-
function shouldUseFromAsSenderFallback(params: {
476-
from?: string | null;
477-
chatType?: string | null;
478-
}): boolean {
479-
const from = normalizeOptionalString(params.from) ?? "";
480-
if (!from) {
481-
return false;
482-
}
483-
const chatType = normalizeLowercaseStringOrEmpty(params.chatType);
484-
if (chatType && chatType !== "direct") {
485-
return false;
486-
}
487-
return !isConversationLikeIdentity(from);
488-
}
489-
490464
function resolveSenderCandidates(params: {
491465
plugin?: ChannelPlugin;
492466
providerId?: ChannelId;

src/auto-reply/reply/reply-elevated.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,32 @@ describe("resolveElevatedPermissions", () => {
7070
});
7171
});
7272

73+
it("does not authorize a group conversation id as a sender identity", () => {
74+
expectAllowFromDecision({
75+
allowFrom: ["120363411111111111@g.us", "from:120363411111111111@g.us"],
76+
allowed: false,
77+
ctx: {
78+
ChatType: "group",
79+
From: "120363411111111111@g.us",
80+
SenderId: "+15550002222",
81+
SenderE164: "+15550002222",
82+
},
83+
});
84+
});
85+
86+
it("keeps direct chat From fallback authorization", () => {
87+
expectAllowFromDecision({
88+
allowFrom: ["from:whatsapp:+15550001111"],
89+
allowed: true,
90+
ctx: {
91+
ChatType: "direct",
92+
From: "whatsapp:+15550001111",
93+
SenderId: undefined,
94+
SenderE164: undefined,
95+
},
96+
});
97+
});
98+
7399
it("does not authorize untyped mutable sender fields", () => {
74100
expectAllowFromDecision({
75101
allowFrom: ["owner-display-name"],

src/auto-reply/reply/reply-elevated.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { normalizeStringEntries } from "@openclaw/normalization-core/string-norm
44
import { resolveAgentConfig } from "../../agents/agent-scope.js";
55
import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/index.js";
66
import type { AgentElevatedAllowFromConfig, OpenClawConfig } from "../../config/config.js";
7+
import { shouldUseFromAsSenderFallback } from "../sender-identity.js";
78
import type { MsgContext } from "../templating.js";
89
import {
910
type AllowFromFormatter,
@@ -94,7 +95,10 @@ function isApprovedElevatedSender(params: {
9495
tokens: senderIdTokens,
9596
});
9697
}
97-
if (senderFrom) {
98+
if (
99+
senderFrom &&
100+
shouldUseFromAsSenderFallback({ from: senderFrom, chatType: params.ctx.ChatType })
101+
) {
98102
addFormattedTokens({
99103
formatAllowFrom: params.formatAllowFrom,
100104
values: [senderFrom, stripSenderPrefix(senderFrom)].filter((value): value is string =>

src/auto-reply/sender-identity.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/** Shared sender identity helpers for authorization checks. */
2+
import {
3+
normalizeLowercaseStringOrEmpty,
4+
normalizeOptionalLowercaseString,
5+
normalizeOptionalString,
6+
} from "@openclaw/normalization-core/string-coerce";
7+
8+
function isConversationLikeIdentity(value: string): boolean {
9+
const normalized = normalizeOptionalLowercaseString(value);
10+
if (!normalized) {
11+
return false;
12+
}
13+
if (normalized.startsWith("chat_id:")) {
14+
return true;
15+
}
16+
return /(^|:)(channel|group|thread|topic|room|space|spaces):/.test(normalized);
17+
}
18+
19+
export function shouldUseFromAsSenderFallback(params: {
20+
from?: string | null;
21+
chatType?: string | null;
22+
}): boolean {
23+
const from = normalizeOptionalString(params.from) ?? "";
24+
if (!from) {
25+
return false;
26+
}
27+
const chatType = normalizeLowercaseStringOrEmpty(params.chatType);
28+
if (chatType && chatType !== "direct") {
29+
return false;
30+
}
31+
return !isConversationLikeIdentity(from);
32+
}

0 commit comments

Comments
 (0)