mirror of
https://fastgit.cc/github.com/openclaw/openclaw
synced 2026-05-01 06:36:23 +08:00
fix(matrix): trust m.mentions.user_ids as authoritative mention source (#64796)
Merged via squash.
Prepared head SHA: 59ca82ef7f
Co-authored-by: hclsys <7755017+hclsys@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
@@ -8,6 +8,8 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Matrix/mentions: keep room mention gating strict while accepting visible `@displayName` Matrix URI labels, so `requireMention` works for non-OpenClaw Matrix clients again. (#64796) Thanks @hclsys.
|
||||
|
||||
## 2026.4.11
|
||||
|
||||
### Changes
|
||||
@@ -350,6 +352,9 @@ Docs: https://docs.openclaw.ai
|
||||
- Reply execution: prefer the active runtime snapshot over stale queued reply config during embedded reply and follow-up execution so SecretRef-backed reply turns stop crashing after secrets have already resolved. (#62693) Thanks @mbelinky.
|
||||
- Android/manual connect: allow blank port input only for TLS manual gateway endpoints so standard HTTPS Tailscale hosts default to `443` without silently changing cleartext manual connects. (#63134) Thanks @Tyler-RNG.
|
||||
- Matrix/agents: hide owner-only `set-profile` from embedded agent channel-action discovery so non-owner runs stop advertising profile updates they cannot execute. (#62662) Thanks @eleqtrizit.
|
||||
- iOS/gateway: replace string-matched connection error UI with structured gateway connection problems, preserve actionable pairing/auth failures over later generic disconnect noise, and surface reusable problem banners and details across onboarding, settings, and root status surfaces. (#62650) Thanks @ngutman.
|
||||
- Git/env sanitization: block additional Git repository-plumbing env variables such as `GIT_DIR`, `GIT_WORK_TREE`, `GIT_COMMON_DIR`, `GIT_INDEX_FILE`, `GIT_OBJECT_DIRECTORY`, `GIT_ALTERNATE_OBJECT_DIRECTORIES`, and `GIT_NAMESPACE` so host-run Git commands cannot be redirected to attacker-chosen repository state through inherited or request-scoped env. (#62002) Thanks @eleqtrizit.
|
||||
- Host exec/env sanitization: block additional request-scoped credential and config-path overrides such as `KUBECONFIG`, cloud credential-path env, `CARGO_HOME`, and `HELM_HOME` so host-run tools can no longer be redirected to attacker-chosen config or state. (#59119) Thanks @eleqtrizit.
|
||||
|
||||
## 2026.4.5
|
||||
|
||||
|
||||
@@ -180,9 +180,9 @@ describe("matrix monitor handler pairing account scope", () => {
|
||||
await handler("!room:example.org", makeEvent("$event1"));
|
||||
await handler("!room:example.org", makeEvent("$event2"));
|
||||
expect(sendMessageMatrixMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessageMatrixMock.mock.calls[0]?.[1]).toContain(
|
||||
"Pairing request is still pending approval.",
|
||||
);
|
||||
const pairingReminder = sendMessageMatrixMock.mock.calls[0]?.[1];
|
||||
expect(typeof pairingReminder).toBe("string");
|
||||
expect(pairingReminder).toContain("Pairing request is still pending approval.");
|
||||
|
||||
await vi.advanceTimersByTimeAsync(5 * 60_000 + 1);
|
||||
await handler("!room:example.org", makeEvent("$event3"));
|
||||
@@ -468,6 +468,30 @@ describe("matrix monitor handler pairing account scope", () => {
|
||||
expect(recordInboundSession).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("processes room messages mentioned via @displayName in Unicode formatted_body", async () => {
|
||||
const recordInboundSession = vi.fn(async () => {});
|
||||
const { handler } = createMatrixHandlerTestHarness({
|
||||
isDirectMessage: false,
|
||||
getMemberDisplayName: async () => "欢欢",
|
||||
recordInboundSession,
|
||||
});
|
||||
|
||||
await handler(
|
||||
"!room:example.org",
|
||||
createMatrixRoomMessageEvent({
|
||||
eventId: "$unicode-display-name-mention",
|
||||
content: {
|
||||
msgtype: "m.text",
|
||||
body: "@欢欢 please reply",
|
||||
formatted_body: '<a href="https://matrix.to/#/@bot:example.org">@欢欢</a> please reply',
|
||||
"m.mentions": { user_ids: ["@bot:example.org"] },
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(recordInboundSession).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not fetch self displayName for plain-text room mentions", async () => {
|
||||
const getMemberDisplayName = vi.fn(async () => "Tom Servo");
|
||||
const { handler, recordInboundSession } = createMatrixHandlerTestHarness({
|
||||
|
||||
@@ -34,15 +34,15 @@ describe("resolveMentions", () => {
|
||||
expect(result.hasExplicitMention).toBe(true);
|
||||
});
|
||||
|
||||
it("does not trust forged m.mentions.user_ids without a visible mention", () => {
|
||||
it("does not trust m.mentions.user_ids without a visible text or formatted mention", () => {
|
||||
const result = resolveMentions({
|
||||
content: {
|
||||
msgtype: "m.text",
|
||||
body: "hello",
|
||||
body: "please reply",
|
||||
"m.mentions": { user_ids: ["@bot:matrix.org"] },
|
||||
},
|
||||
userId,
|
||||
text: "hello",
|
||||
text: "please reply",
|
||||
mentionRegexes,
|
||||
});
|
||||
expect(result.wasMentioned).toBe(false);
|
||||
@@ -209,6 +209,24 @@ describe("resolveMentions", () => {
|
||||
expect(result.wasMentioned).toBe(true);
|
||||
});
|
||||
|
||||
it("detects mention when the visible label is @displayName with Unicode text", () => {
|
||||
const result = resolveMentions({
|
||||
content: {
|
||||
msgtype: "m.text",
|
||||
body: "@欢欢 please reply",
|
||||
formatted_body:
|
||||
'<a href="https://matrix.to/#/@huanhuan:localhost">@欢欢</a> please reply',
|
||||
"m.mentions": { user_ids: ["@huanhuan:localhost"] },
|
||||
},
|
||||
userId: "@huanhuan:localhost",
|
||||
displayName: "欢欢",
|
||||
text: "@欢欢 please reply",
|
||||
mentionRegexes: [],
|
||||
});
|
||||
expect(result.wasMentioned).toBe(true);
|
||||
expect(result.hasExplicitMention).toBe(true);
|
||||
});
|
||||
|
||||
it("ignores out-of-range hexadecimal HTML entities in visible labels", () => {
|
||||
expect(() =>
|
||||
resolveMentions({
|
||||
|
||||
@@ -81,6 +81,7 @@ function isVisibleMentionLabel(params: {
|
||||
localpart ? extractVisibleMentionText(localpart) : null,
|
||||
localpart ? extractVisibleMentionText(`@${localpart}`) : null,
|
||||
params.displayName ? extractVisibleMentionText(params.displayName) : null,
|
||||
params.displayName ? extractVisibleMentionText(`@${params.displayName}`) : null,
|
||||
].filter((value): value is string => Boolean(value));
|
||||
return candidates.includes(cleaned);
|
||||
}
|
||||
@@ -163,6 +164,9 @@ export function resolveMentions(params: {
|
||||
mentionRegexes: params.mentionRegexes,
|
||||
})
|
||||
: false;
|
||||
// Matrix clients can mention users through m.mentions metadata plus a visible
|
||||
// Matrix URI label in formatted_body. Keep the visible-mention requirement so
|
||||
// hidden metadata-only mentions do not trigger the handler.
|
||||
const metadataBackedUserMention = Boolean(
|
||||
params.userId &&
|
||||
mentionedUsers.has(params.userId) &&
|
||||
|
||||
Reference in New Issue
Block a user