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
This commit is contained in:
AO Bot 2026-03-23 21:01:09 +00:00
parent 36d354e0c2
commit 3716fc751c
5 changed files with 16 additions and 6 deletions

View File

@ -848,6 +848,7 @@ export default function (api: any) {
let healthInterval: ReturnType<typeof setInterval> | null = null;
let boardScanInterval: ReturnType<typeof setInterval> | null = null;
let boardScanInitialTimeout: ReturnType<typeof setTimeout> | null = null;
let lastKnownIssueIds: Set<number> = 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;

View File

@ -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);
});
});
});

View File

@ -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)) {

View File

@ -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 () => {

View File

@ -198,7 +198,7 @@ export function create(config?: Record<string, unknown>): Notifier {
if (!webhookUrl) return null;
const payload: Record<string, unknown> = { 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;
},