mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(irc): recognize punctuation in nickname mentions (#116758)
Co-authored-by: Peter Steinberger <steipete@macos.shared>
This commit is contained in:
committed by
GitHub
parent
58580fff2f
commit
873bcc2985
@@ -334,6 +334,70 @@ describe("irc inbound behavior", () => {
|
||||
expect(ctx?.OriginatingTo).toBe("channel:#ops");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ label: "ordinary nick", nick: "OpenClaw", text: "OpenClaw: hello", mentioned: true },
|
||||
{ label: "ASCII case folding", nick: "OpenClaw", text: "openclaw: hello", mentioned: true },
|
||||
{ label: "leading bracket", nick: "[Claw]", text: "[Claw]: hello", mentioned: true },
|
||||
{ label: "trailing bracket", nick: "Claw]", text: "hello Claw],", mentioned: true },
|
||||
{ label: "leading caret", nick: "^Claw", text: "^Claw, hello", mentioned: true },
|
||||
{ label: "trailing hyphen", nick: "Claw-", text: "Claw-: hello", mentioned: true },
|
||||
{ label: "escaped backslash", nick: "\\Claw", text: "\\Claw: hello", mentioned: true },
|
||||
{ label: "embedded brackets", nick: "Claw[Ops]", text: "Claw[Ops]: hi", mentioned: true },
|
||||
{ label: "RFC1459 opening bracket", nick: "[Claw", text: "{claw: hello", mentioned: true },
|
||||
{ label: "RFC1459 opening brace", nick: "{Claw", text: "[claw: hello", mentioned: true },
|
||||
{ label: "RFC1459 closing bracket", nick: "Claw]", text: "claw}: hello", mentioned: true },
|
||||
{ label: "RFC1459 closing brace", nick: "Claw}", text: "claw]: hello", mentioned: true },
|
||||
{ label: "RFC1459 backslash", nick: "\\Claw", text: "|claw: hello", mentioned: true },
|
||||
{ label: "RFC1459 vertical bar", nick: "|Claw", text: "\\claw: hello", mentioned: true },
|
||||
{ label: "RFC1459 caret", nick: "^Claw", text: "~claw: hello", mentioned: true },
|
||||
{ label: "RFC1459 tilde", nick: "~Claw", text: "^claw: hello", mentioned: true },
|
||||
{ label: "ordinary nick suffix", nick: "Claw", text: "Clawbot: hello", mentioned: false },
|
||||
{ label: "ordinary nick prefix", nick: "Claw", text: "overClaw: hello", mentioned: false },
|
||||
{ label: "IRC nick punctuation suffix", nick: "Claw", text: "Claw-bot: hi", mentioned: false },
|
||||
{ label: "RFC1459 tilde nick suffix", nick: "Claw", text: "Claw~bot: hi", mentioned: false },
|
||||
{
|
||||
label: "punctuated nick inside a longer nick",
|
||||
nick: "[Claw]",
|
||||
text: "prefix[Claw]: hello",
|
||||
mentioned: false,
|
||||
},
|
||||
])(
|
||||
"recognizes only complete IRC nickname mentions: $label",
|
||||
async ({ nick, text, mentioned }) => {
|
||||
const coreRuntime = createPluginRuntimeMock();
|
||||
const runtime = createRuntimeEnv();
|
||||
setIrcRuntime(coreRuntime as never);
|
||||
|
||||
await handleIrcInbound({
|
||||
message: createMessage({
|
||||
target: "#ops",
|
||||
isGroup: true,
|
||||
text,
|
||||
}),
|
||||
account: createAccount({
|
||||
nick,
|
||||
config: {
|
||||
dmPolicy: "open",
|
||||
allowFrom: ["*"],
|
||||
groupPolicy: "open",
|
||||
groupAllowFrom: [],
|
||||
groups: {
|
||||
"#ops": { enabled: true, requireMention: true },
|
||||
},
|
||||
},
|
||||
}),
|
||||
config: { channels: { irc: {} } } as CoreConfig,
|
||||
runtime,
|
||||
sendReply: vi.fn(async () => {}),
|
||||
});
|
||||
|
||||
expect(coreRuntime.channel.inbound.dispatch).toHaveBeenCalledTimes(mentioned ? 1 : 0);
|
||||
if (!mentioned) {
|
||||
expect(runtime.log).toHaveBeenCalledWith("irc: drop channel #ops (missing-mention)");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("drops a spoofed sender for a host-less nick!user DM allowlist entry", async () => {
|
||||
const coreRuntime = createPluginRuntimeMock();
|
||||
const runtime = createRuntimeEnv();
|
||||
|
||||
@@ -81,6 +81,27 @@ const ircIngressIdentity = defineStableChannelIngressIdentity({
|
||||
});
|
||||
|
||||
const escapeIrcRegexLiteral = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
// IRC nicknames permit punctuation, so ASCII word boundaries lose valid leading/trailing chars.
|
||||
const IRC_NICK_CHARACTER = String.raw`[A-Za-z0-9_\-\[\]\\\x60^{}|~]`;
|
||||
const IRC_RFC1459_CASE_EQUIVALENTS = new Map([
|
||||
["[", "{"],
|
||||
["{", "["],
|
||||
["]", "}"],
|
||||
["}", "]"],
|
||||
["\\", "|"],
|
||||
["|", "\\"],
|
||||
["^", "~"],
|
||||
["~", "^"],
|
||||
]);
|
||||
|
||||
function buildIrcNickMentionPattern(value: string): string {
|
||||
return Array.from(value, (character) => {
|
||||
const equivalent = IRC_RFC1459_CASE_EQUIVALENTS.get(character);
|
||||
return equivalent
|
||||
? `[${escapeIrcRegexLiteral(character)}${escapeIrcRegexLiteral(equivalent)}]`
|
||||
: escapeIrcRegexLiteral(character);
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function isBareNick(value: string): boolean {
|
||||
return !value.includes("!") && !value.includes("@");
|
||||
@@ -266,7 +287,10 @@ export async function handleIrcInbound(params: {
|
||||
const mentionRegexes = core.channel.mentions.buildMentionRegexes(config as OpenClawConfig);
|
||||
const mentionNick = connectedNick?.trim() || account.nick;
|
||||
const explicitMentionRegex = mentionNick
|
||||
? new RegExp(`\\b${escapeIrcRegexLiteral(mentionNick)}\\b[:,]?`, "i")
|
||||
? new RegExp(
|
||||
`(?<!${IRC_NICK_CHARACTER})${buildIrcNickMentionPattern(mentionNick)}(?!${IRC_NICK_CHARACTER})[:,]?`,
|
||||
"i",
|
||||
)
|
||||
: null;
|
||||
const wasMentioned =
|
||||
core.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes) ||
|
||||
|
||||
Reference in New Issue
Block a user