From abf2dca03d53dba3f6ec4cd3bdc7e0239d810652 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Wed, 3 Jun 2026 11:59:26 +0100 Subject: [PATCH] fix(build): avoid shell in build scripts to resolve CodeQL shell-injection alerts Remove the string-form execSync invocations flagged by CodeQL js/shell-command-injection-from-environment at two build-tooling call sites: - scripts/build-modules.js: prettier --write now runs via execFileSync(process.execPath, [require.resolve("prettier/bin/prettier.cjs"), "--write", join(BUILTIN_DIR, "*.js")]). Prettier expands the glob itself, so no shell parses the interpolated path. - scripts/bash-bundle/build.mjs: the esbuild bundle step now uses esbuild's JS API (await build({ ... })) instead of spawning a CLI through a shell. The CLI --alias flags are mapped to the alias option and every other flag to its matching build option (bundle, format, platform, target, mainFields, outfile, minify, treeShaking). This removes the child process entirely and is portable: esbuild's CLI bin is a native binary on Linux, so it cannot be launched via `node`. Build tooling only; the generated bash.js bundle is byte-for-byte identical and no other generated artifacts change. Resolves CodeQL alerts #2 and #11. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Simon Davies --- scripts/bash-bundle/build.mjs | 65 +++++++++++++++++++---------------- scripts/build-modules.js | 23 +++++++++---- 2 files changed, 53 insertions(+), 35 deletions(-) diff --git a/scripts/bash-bundle/build.mjs b/scripts/bash-bundle/build.mjs index b409bdf..e3e3acd 100644 --- a/scripts/bash-bundle/build.mjs +++ b/scripts/bash-bundle/build.mjs @@ -8,7 +8,7 @@ // // Prerequisites: npm install (just-bash and esbuild must be in node_modules) -import { execSync } from "node:child_process"; +import { build } from "esbuild"; import { readFileSync, writeFileSync, existsSync, unlinkSync } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; @@ -32,37 +32,44 @@ if (!existsSync(join(repoRoot, "node_modules", "just-bash"))) { process.exit(1); } -const aliasArgs = [ - `--alias:node:module=${join(stubDir, "module-stub.mjs")}`, - `--alias:node:zlib=${join(stubDir, "zlib-stub.mjs")}`, - `--alias:node:worker_threads=${join(stubDir, "worker-stub.mjs")}`, - `--alias:node:path=${join(stubDir, "node-path-stub.mjs")}`, - `--alias:node:dns=${join(stubDir, "dns-stub.mjs")}`, - `--alias:node:crypto=${join(stubDir, "crypto-stub.mjs")}`, - `--alias:node:url=${join(stubDir, "url-stub.mjs")}`, - `--alias:node:fs=${join(stubDir, "fs-stub.mjs")}`, - `--alias:node:fs/promises=${join(stubDir, "fs-stub.mjs")}`, - `--alias:node:child_process=${join(stubDir, "worker-stub.mjs")}`, - `--alias:node:os=${join(stubDir, "worker-stub.mjs")}`, - `--alias:node:async_hooks=${join(stubDir, "worker-stub.mjs")}`, - `--alias:turndown=${join(stubDir, "turndown-stub.mjs")}`, - `--alias:seek-bzip=${join(stubDir, "bzip-stub.mjs")}`, - `--alias:node-liblzma=${join(stubDir, "liblzma-stub.mjs")}`, - `--alias:@mongodb-js/zstd=${join(stubDir, "zstd-stub.mjs")}`, - `--alias:sql.js=${join(stubDir, "sqljs-stub.mjs")}`, -].join(" "); +const alias = { + "node:module": join(stubDir, "module-stub.mjs"), + "node:zlib": join(stubDir, "zlib-stub.mjs"), + "node:worker_threads": join(stubDir, "worker-stub.mjs"), + "node:path": join(stubDir, "node-path-stub.mjs"), + "node:dns": join(stubDir, "dns-stub.mjs"), + "node:crypto": join(stubDir, "crypto-stub.mjs"), + "node:url": join(stubDir, "url-stub.mjs"), + "node:fs": join(stubDir, "fs-stub.mjs"), + "node:fs/promises": join(stubDir, "fs-stub.mjs"), + "node:child_process": join(stubDir, "worker-stub.mjs"), + "node:os": join(stubDir, "worker-stub.mjs"), + "node:async_hooks": join(stubDir, "worker-stub.mjs"), + turndown: join(stubDir, "turndown-stub.mjs"), + "seek-bzip": join(stubDir, "bzip-stub.mjs"), + "node-liblzma": join(stubDir, "liblzma-stub.mjs"), + "@mongodb-js/zstd": join(stubDir, "zstd-stub.mjs"), + "sql.js": join(stubDir, "sqljs-stub.mjs"), +}; const tmpBundle = join(stubDir, "_tmp_bundle.js"); -execSync( - `npx esbuild ${entryFile} ` + - `--bundle --format=esm --platform=neutral --target=es2020 ` + - `--main-fields=module,main ` + - `${aliasArgs} ` + - `--outfile=${tmpBundle} ` + - `--minify --tree-shaking=true`, - { stdio: "inherit", cwd: repoRoot }, -); +// Use esbuild's JS API rather than spawning the CLI. This avoids passing +// interpolated paths through a shell entirely (resolving the CodeQL +// shell-injection alert) and is portable: the esbuild CLI bin is a native +// binary on some platforms, so it can't be launched via `node`. +await build({ + entryPoints: [entryFile], + bundle: true, + format: "esm", + platform: "neutral", + target: "es2020", + mainFields: ["module", "main"], + alias, + outfile: tmpBundle, + minify: true, + treeShaking: true, +}); // ── Step 2: Prepend polyfills ─────────────────────────────────────── diff --git a/scripts/build-modules.js b/scripts/build-modules.js index 9f0da01..c69933f 100644 --- a/scripts/build-modules.js +++ b/scripts/build-modules.js @@ -12,12 +12,14 @@ * 7. Regenerates host-modules.d.ts */ -import { execSync } from "child_process"; +import { execSync, execFileSync } from "child_process"; import { join, dirname } from "path"; import { fileURLToPath } from "url"; +import { createRequire } from "module"; import { existsSync, unlinkSync, readdirSync, statSync } from "fs"; const __dirname = dirname(fileURLToPath(import.meta.url)); +const require = createRequire(import.meta.url); const ROOT = join(__dirname, ".."); const BUILTIN_DIR = join(ROOT, "builtin-modules"); const PLUGINS_DIR = join(ROOT, "plugins"); @@ -68,10 +70,20 @@ execSync("npx tsx scripts/generate-ha-modules-dts.ts", { }); // Step 4: Format with Prettier -execSync(`prettier --write "${BUILTIN_DIR}/*.js"`, { - cwd: ROOT, - stdio: "inherit", -}); +// Invoke prettier without a shell (execFileSync) so the interpolated path +// can't be interpreted as a shell command. Prettier expands the glob itself. +execFileSync( + process.execPath, + [ + require.resolve("prettier/bin/prettier.cjs"), + "--write", + join(BUILTIN_DIR, "*.js"), + ], + { + cwd: ROOT, + stdio: "inherit", + }, +); console.log("\nUpdating module hashes..."); @@ -161,5 +173,4 @@ execSync("npx tsx scripts/generate-host-modules-dts.ts", { stdio: "inherit", }); - console.log("✓ Build complete");