From 7dab7e16389ec10c82e8a926b47d0c2fe397c989 Mon Sep 17 00:00:00 2001 From: Mark Goldenstein Date: Sat, 1 Aug 2026 18:32:33 -0700 Subject: [PATCH 1/2] Fix open CodeQL findings --- scripts/e2e/oca-codex-telegram-proof.ts | 27 ++++++++++++++++++++----- src/config.ts | 6 +++++- tests/config.test.ts | 6 ++++++ tests/oca-codex-telegram-proof.test.ts | 6 ++++++ 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/scripts/e2e/oca-codex-telegram-proof.ts b/scripts/e2e/oca-codex-telegram-proof.ts index bc7b5d56..f8391b97 100644 --- a/scripts/e2e/oca-codex-telegram-proof.ts +++ b/scripts/e2e/oca-codex-telegram-proof.ts @@ -2,7 +2,9 @@ import { spawn, spawnSync } from "node:child_process"; import { createHash, randomUUID } from "node:crypto"; import { + accessSync, chmodSync, + constants, copyFileSync, existsSync, mkdirSync, @@ -386,12 +388,27 @@ export function resolveProofOutputDir(outputDir: string): string { return resolved; } -function commandExists(command: string): boolean { - if (command.includes("/") || command.includes("\\")) return existsSync(expandHome(command)); - const result = spawnSync("sh", ["-c", `command -v "$1" >/dev/null 2>&1`, "sh", command], { - stdio: "ignore", +export function commandExists(command: string): boolean { + const candidates = command.includes("/") || command.includes("\\") + ? [expandHome(command)] + : (process.env.PATH ?? "") + .split(path.delimiter) + .filter(Boolean) + .flatMap((directory) => { + const base = path.join(directory, command); + if (process.platform !== "win32") return [base]; + const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean); + return [base, ...extensions.map((extension) => `${base}${extension.toLowerCase()}`)]; + }); + + return candidates.some((candidate) => { + try { + accessSync(candidate, constants.X_OK); + return statSync(candidate).isFile(); + } catch { + return false; + } }); - return result.status === 0; } function shellQuote(value: string): string { diff --git a/src/config.ts b/src/config.ts index 21eaad33..74f7724b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -372,7 +372,11 @@ export function resolveAgentChannel(workdir: string): string | undefined { const mapping = pluginConfig.agentChannels; if (!mapping) return undefined; - const normalise = (p: string) => p.replace(/\/+$/, ""); + const normalise = (p: string) => { + let end = p.length; + while (end > 0 && p[end - 1] === "/") end -= 1; + return p.slice(0, end); + }; const normWorkdir = normalise(workdir); const entries = Object.entries(mapping).sort((a, b) => b[0].length - a[0].length); diff --git a/tests/config.test.ts b/tests/config.test.ts index 9e798273..d80d3b27 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -47,6 +47,12 @@ describe("resolveAgentChannel", () => { assert.equal(resolveAgentChannel("/home/user"), "telegram|bot1|123"); }); + it("normalizes long runs of trailing slashes in linear time", () => { + const trailingSlashes = "/".repeat(100_000); + setPluginConfig({ agentChannels: { [`/home/user${trailingSlashes}`]: "telegram|bot1|123" } }); + assert.equal(resolveAgentChannel(`/home/user${trailingSlashes}`), "telegram|bot1|123"); + }); + it("returns undefined for non-matching path", () => { setPluginConfig({ agentChannels: { "/home/user/project": "telegram|bot1|123" } }); assert.equal(resolveAgentChannel("/other/path"), undefined); diff --git a/tests/oca-codex-telegram-proof.test.ts b/tests/oca-codex-telegram-proof.test.ts index 82b7568e..8010cb93 100644 --- a/tests/oca-codex-telegram-proof.test.ts +++ b/tests/oca-codex-telegram-proof.test.ts @@ -5,6 +5,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync import { tmpdir } from "node:os"; import { join } from "node:path"; import { + commandExists, buildProofPlan, collectDoctorChecks, parseArgs, @@ -102,6 +103,11 @@ describe("OCA Codex Telegram proof runner", () => { } }); + it("checks executable paths without interpreting shell syntax", () => { + assert.equal(commandExists(process.execPath), true); + assert.equal(commandExists("definitely-missing-command; true"), false); + }); + it("prints a redacted proof plan without exposing Convex secret values", () => { const originalSecret = process.env.OPENCLAW_QA_CONVEX_SECRET_CI; const originalSite = process.env.OPENCLAW_QA_CONVEX_SITE_URL; From 03c99c108b26240253b2a1a8faf11ce45939adcf Mon Sep 17 00:00:00 2001 From: Mark Goldenstein Date: Sat, 1 Aug 2026 18:56:01 -0700 Subject: [PATCH 2/2] Preserve empty PATH lookup entries --- scripts/e2e/oca-codex-telegram-proof.ts | 18 +++++++++--------- tests/oca-codex-telegram-proof.test.ts | 23 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/scripts/e2e/oca-codex-telegram-proof.ts b/scripts/e2e/oca-codex-telegram-proof.ts index f8391b97..fae838a1 100644 --- a/scripts/e2e/oca-codex-telegram-proof.ts +++ b/scripts/e2e/oca-codex-telegram-proof.ts @@ -389,17 +389,17 @@ export function resolveProofOutputDir(outputDir: string): string { } export function commandExists(command: string): boolean { + const pathEntries = process.env.PATH === undefined + ? (process.platform === "win32" ? [] : ["/usr/bin", "/bin"]) + : process.env.PATH.split(path.delimiter).map((directory) => directory || "."); const candidates = command.includes("/") || command.includes("\\") ? [expandHome(command)] - : (process.env.PATH ?? "") - .split(path.delimiter) - .filter(Boolean) - .flatMap((directory) => { - const base = path.join(directory, command); - if (process.platform !== "win32") return [base]; - const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean); - return [base, ...extensions.map((extension) => `${base}${extension.toLowerCase()}`)]; - }); + : pathEntries.flatMap((directory) => { + const base = path.join(directory, command); + if (process.platform !== "win32") return [base]; + const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean); + return [base, ...extensions.map((extension) => `${base}${extension.toLowerCase()}`)]; + }); return candidates.some((candidate) => { try { diff --git a/tests/oca-codex-telegram-proof.test.ts b/tests/oca-codex-telegram-proof.test.ts index 8010cb93..54098ddf 100644 --- a/tests/oca-codex-telegram-proof.test.ts +++ b/tests/oca-codex-telegram-proof.test.ts @@ -1,9 +1,9 @@ import { describe, it } from "node:test"; import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; -import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { delimiter, join } from "node:path"; import { commandExists, buildProofPlan, @@ -108,6 +108,25 @@ describe("OCA Codex Telegram proof runner", () => { assert.equal(commandExists("definitely-missing-command; true"), false); }); + it("preserves POSIX empty PATH entries as the current directory", { skip: process.platform === "win32" }, () => { + const originalCwd = process.cwd(); + const originalPath = process.env.PATH; + const directory = mkdtempSync(join(tmpdir(), "oca-proof-command-path-")); + const command = "oca-proof-local-command"; + try { + writeFileSync(join(directory, command), "#!/bin/sh\nexit 0\n"); + chmodSync(join(directory, command), 0o700); + process.chdir(directory); + process.env.PATH = `${delimiter}${originalPath ?? ""}`; + assert.equal(commandExists(command), true); + } finally { + process.chdir(originalCwd); + if (originalPath === undefined) delete process.env.PATH; + else process.env.PATH = originalPath; + rmSync(directory, { recursive: true, force: true }); + } + }); + it("prints a redacted proof plan without exposing Convex secret values", () => { const originalSecret = process.env.OPENCLAW_QA_CONVEX_SECRET_CI; const originalSite = process.env.OPENCLAW_QA_CONVEX_SITE_URL;