From 3716fc751c271b043a02c2fbb71008acbe15f032 Mon Sep 17 00:00:00 2001 From: AO Bot Date: Mon, 23 Mar 2026 21:01:09 +0000 Subject: [PATCH] fix: address Cursor Bugbot review comments on PR #631 - Fix shell injection in writeShellExport: use single-quoted token with escaped embedded single quotes instead of double-quoted interpolation - Fix board scanner initial setTimeout not cleared on stop: store the timeout handle and clear it in the stop handler - Fix openclaw-probe test asserting deliver:true when code sends false - Fix Discord notifier thread_id test to check URL query param instead of body, and remove redundant thread_id from post() body payload --- openclaw-plugin/index.ts | 7 ++++++- packages/cli/__tests__/lib/openclaw-probe.test.ts | 2 +- packages/cli/src/commands/setup.ts | 6 +++++- packages/plugins/notifier-discord/src/index.test.ts | 5 +++-- packages/plugins/notifier-discord/src/index.ts | 2 +- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/openclaw-plugin/index.ts b/openclaw-plugin/index.ts index d71ae79b9..657a8427b 100644 --- a/openclaw-plugin/index.ts +++ b/openclaw-plugin/index.ts @@ -848,6 +848,7 @@ export default function (api: any) { let healthInterval: ReturnType | null = null; let boardScanInterval: ReturnType | null = null; + let boardScanInitialTimeout: ReturnType | null = null; let lastKnownIssueIds: Set = new Set(); let isFirstBoardScan = true; @@ -922,10 +923,14 @@ export default function (api: any) { } }; - setTimeout(scan, 10_000); + boardScanInitialTimeout = setTimeout(scan, 10_000); boardScanInterval = setInterval(scan, scanMs); }, stop: async () => { + if (boardScanInitialTimeout) { + clearTimeout(boardScanInitialTimeout); + boardScanInitialTimeout = null; + } if (boardScanInterval) { clearInterval(boardScanInterval); boardScanInterval = null; diff --git a/packages/cli/__tests__/lib/openclaw-probe.test.ts b/packages/cli/__tests__/lib/openclaw-probe.test.ts index 5395ea531..037d428a2 100644 --- a/packages/cli/__tests__/lib/openclaw-probe.test.ts +++ b/packages/cli/__tests__/lib/openclaw-probe.test.ts @@ -172,7 +172,7 @@ describe("openclaw-probe", () => { expect(body.name).toBe("AO"); expect(body.sessionKey).toBe("hook:ao:setup-test"); expect(body.wakeMode).toBe("now"); - expect(body.deliver).toBe(true); + expect(body.deliver).toBe(false); }); }); }); diff --git a/packages/cli/src/commands/setup.ts b/packages/cli/src/commands/setup.ts index 813ebb5b1..21e3d6735 100644 --- a/packages/cli/src/commands/setup.ts +++ b/packages/cli/src/commands/setup.ts @@ -321,7 +321,11 @@ function writeShellExport(token: string): string | undefined { const profileName = shell.endsWith("/zsh") ? ".zshrc" : ".bashrc"; const profilePath = join(homedir(), profileName); - const exportLine = `export OPENCLAW_HOOKS_TOKEN="${token}"`; + // Sanitize token: escape shell-special characters to prevent injection + // when the profile is sourced. Single-quote the value and escape any + // embedded single quotes (the only character that breaks '...' quoting). + const safeToken = token.replace(/'/g, "'\\''"); + const exportLine = `export OPENCLAW_HOOKS_TOKEN='${safeToken}'`; // Check if it already exists if (existsSync(profilePath)) { diff --git a/packages/plugins/notifier-discord/src/index.test.ts b/packages/plugins/notifier-discord/src/index.test.ts index cd9879663..c2af7d61f 100644 --- a/packages/plugins/notifier-discord/src/index.test.ts +++ b/packages/plugins/notifier-discord/src/index.test.ts @@ -162,8 +162,9 @@ describe("notifier-discord", () => { }); await notifier.notify(makeEvent()); - const body = JSON.parse(fetchMock.mock.calls[0][1].body); - expect(body.thread_id).toBe("1234567890"); + // Discord requires thread_id as a URL query param, not in the JSON body + const calledUrl = fetchMock.mock.calls[0][0]; + expect(calledUrl).toBe("https://discord.com/api/webhooks/123/abc?thread_id=1234567890"); }); it("is a no-op when webhookUrl not configured", async () => { diff --git a/packages/plugins/notifier-discord/src/index.ts b/packages/plugins/notifier-discord/src/index.ts index ceee8e6d8..5b43c7462 100644 --- a/packages/plugins/notifier-discord/src/index.ts +++ b/packages/plugins/notifier-discord/src/index.ts @@ -198,7 +198,7 @@ export function create(config?: Record): Notifier { if (!webhookUrl) return null; const payload: Record = { username, content: message }; if (avatarUrl) payload.avatar_url = avatarUrl; - if (threadId) payload.thread_id = threadId; + // thread_id is already passed as a URL query param via effectiveUrl await postWithRetry(effectiveUrl!, payload, retries, retryDelayMs); return null; },