mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix(mattermost): keep slash command error truncation UTF-16 safe (#102607)
* fix(mattermost): keep slash command error truncation UTF-16 safe Replace `.slice(0, 300)` and `.slice(0, 200)` with `truncateUtf16Safe` in sanitizeCommandLookupError and sanitizeMattermostLogValue to prevent surrogate pair corruption. Ref. lsr911 pattern — mechanical substitution, no behavior change. * test(mattermost): cover UTF-16-safe slash logs Co-authored-by: 黄剑雄0668001315 <huang.jianxiong@xydigit.com> --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
parent
fb52a989b2
commit
ac71074807
2 changed files with 41 additions and 8 deletions
|
|
@ -713,8 +713,9 @@ describe("slash-http", () => {
|
|||
|
||||
it("logs when command lookup by id returns a deleted command before fallback", async () => {
|
||||
const registeredCommand = createRegisteredCommand();
|
||||
const commandId = `${"i".repeat(199)}😀tail`;
|
||||
const command = {
|
||||
id: "cmd-1\r\nspoofed",
|
||||
id: commandId,
|
||||
token: "valid-token",
|
||||
team_id: "t1",
|
||||
trigger: "oc_status",
|
||||
|
|
@ -748,9 +749,9 @@ describe("slash-http", () => {
|
|||
|
||||
expect(log).toHaveBeenCalledTimes(1);
|
||||
const message = firstLogMessage(log);
|
||||
expect(message).not.toMatch(/[\r\n\t]/u);
|
||||
expect(message).toContain("deleted command cmd-1 spoofed");
|
||||
expect(message).toContain("using team list fallback");
|
||||
expect(message).toBe(
|
||||
`mattermost: slash command lookup by id returned deleted command ${"i".repeat(199)} for /oc_status; using team list fallback`,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects current commands with a mismatched method or callback URL", async () => {
|
||||
|
|
@ -923,4 +924,35 @@ describe("slash-http", () => {
|
|||
expect(message).not.toContain("secret-query");
|
||||
expect(message).not.toContain("user:pass");
|
||||
});
|
||||
|
||||
it("keeps upstream lookup error previews UTF-16 safe", async () => {
|
||||
const registeredCommand = createRegisteredCommand();
|
||||
const client = createCommandLookupClient({
|
||||
commandLookupError: new Error("primary failure"),
|
||||
listLookupError: new Error(`${"e".repeat(299)}😀tail`),
|
||||
});
|
||||
const log = vi.fn();
|
||||
|
||||
await expect(
|
||||
validateMattermostSlashCommandToken({
|
||||
accountId: "default",
|
||||
client,
|
||||
registeredCommand,
|
||||
payload: {
|
||||
token: "valid-token",
|
||||
team_id: "t1",
|
||||
channel_id: "c1",
|
||||
user_id: "u1",
|
||||
command: "/oc_status",
|
||||
text: "",
|
||||
},
|
||||
log,
|
||||
}),
|
||||
).resolves.toBe(false);
|
||||
|
||||
expect(log).toHaveBeenCalledTimes(1);
|
||||
expect(firstLogMessage(log)).toBe(
|
||||
`mattermost: slash command registration check failed for /oc_status: ${"e".repeat(299)}; command lookup: primary failure`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import type { ResolvedMattermostAccount } from "../mattermost/accounts.js";
|
||||
import { getMattermostRuntime } from "../runtime.js";
|
||||
import {
|
||||
|
|
@ -142,7 +143,7 @@ function isDeletedMattermostCommand(command: { delete_at?: number }): boolean {
|
|||
|
||||
function sanitizeCommandLookupError(error: unknown): string {
|
||||
const raw = error instanceof Error ? error.message : String(error);
|
||||
return raw
|
||||
const sanitized = raw
|
||||
.replace(/[\r\n\t]/gu, " ")
|
||||
.replace(/https?:\/\/[^\s)\]}]+/giu, (urlText) => {
|
||||
try {
|
||||
|
|
@ -165,12 +166,12 @@ function sanitizeCommandLookupError(error: unknown): string {
|
|||
.replace(
|
||||
/\b(token|authorization|access_token|refresh_token|client_secret|botToken)\b(\s*["']?\s*(?:=|:)\s*["']?)[^"',\s;}]+/giu,
|
||||
"$1$2[redacted]",
|
||||
)
|
||||
.slice(0, 300);
|
||||
);
|
||||
return truncateUtf16Safe(sanitized, 300);
|
||||
}
|
||||
|
||||
function sanitizeMattermostLogValue(value: string): string {
|
||||
return value.replace(/[\r\n\t]/gu, " ").slice(0, 200);
|
||||
return truncateUtf16Safe(value.replace(/[\r\n\t]/gu, " "), 200);
|
||||
}
|
||||
|
||||
async function withCommandLookupTimeout<T>(task: (signal: AbortSignal) => Promise<T>): Promise<T> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue