Skip to content

Commit abf2dca

Browse files
simongdaviesCopilot
andcommitted
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 <simongdavies@users.noreply.github.com>
1 parent 1a7ea63 commit abf2dca

2 files changed

Lines changed: 53 additions & 35 deletions

File tree

scripts/bash-bundle/build.mjs

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//
99
// Prerequisites: npm install (just-bash and esbuild must be in node_modules)
1010

11-
import { execSync } from "node:child_process";
11+
import { build } from "esbuild";
1212
import { readFileSync, writeFileSync, existsSync, unlinkSync } from "node:fs";
1313
import { join, dirname } from "node:path";
1414
import { fileURLToPath } from "node:url";
@@ -32,37 +32,44 @@ if (!existsSync(join(repoRoot, "node_modules", "just-bash"))) {
3232
process.exit(1);
3333
}
3434

35-
const aliasArgs = [
36-
`--alias:node:module=${join(stubDir, "module-stub.mjs")}`,
37-
`--alias:node:zlib=${join(stubDir, "zlib-stub.mjs")}`,
38-
`--alias:node:worker_threads=${join(stubDir, "worker-stub.mjs")}`,
39-
`--alias:node:path=${join(stubDir, "node-path-stub.mjs")}`,
40-
`--alias:node:dns=${join(stubDir, "dns-stub.mjs")}`,
41-
`--alias:node:crypto=${join(stubDir, "crypto-stub.mjs")}`,
42-
`--alias:node:url=${join(stubDir, "url-stub.mjs")}`,
43-
`--alias:node:fs=${join(stubDir, "fs-stub.mjs")}`,
44-
`--alias:node:fs/promises=${join(stubDir, "fs-stub.mjs")}`,
45-
`--alias:node:child_process=${join(stubDir, "worker-stub.mjs")}`,
46-
`--alias:node:os=${join(stubDir, "worker-stub.mjs")}`,
47-
`--alias:node:async_hooks=${join(stubDir, "worker-stub.mjs")}`,
48-
`--alias:turndown=${join(stubDir, "turndown-stub.mjs")}`,
49-
`--alias:seek-bzip=${join(stubDir, "bzip-stub.mjs")}`,
50-
`--alias:node-liblzma=${join(stubDir, "liblzma-stub.mjs")}`,
51-
`--alias:@mongodb-js/zstd=${join(stubDir, "zstd-stub.mjs")}`,
52-
`--alias:sql.js=${join(stubDir, "sqljs-stub.mjs")}`,
53-
].join(" ");
35+
const alias = {
36+
"node:module": join(stubDir, "module-stub.mjs"),
37+
"node:zlib": join(stubDir, "zlib-stub.mjs"),
38+
"node:worker_threads": join(stubDir, "worker-stub.mjs"),
39+
"node:path": join(stubDir, "node-path-stub.mjs"),
40+
"node:dns": join(stubDir, "dns-stub.mjs"),
41+
"node:crypto": join(stubDir, "crypto-stub.mjs"),
42+
"node:url": join(stubDir, "url-stub.mjs"),
43+
"node:fs": join(stubDir, "fs-stub.mjs"),
44+
"node:fs/promises": join(stubDir, "fs-stub.mjs"),
45+
"node:child_process": join(stubDir, "worker-stub.mjs"),
46+
"node:os": join(stubDir, "worker-stub.mjs"),
47+
"node:async_hooks": join(stubDir, "worker-stub.mjs"),
48+
turndown: join(stubDir, "turndown-stub.mjs"),
49+
"seek-bzip": join(stubDir, "bzip-stub.mjs"),
50+
"node-liblzma": join(stubDir, "liblzma-stub.mjs"),
51+
"@mongodb-js/zstd": join(stubDir, "zstd-stub.mjs"),
52+
"sql.js": join(stubDir, "sqljs-stub.mjs"),
53+
};
5454

5555
const tmpBundle = join(stubDir, "_tmp_bundle.js");
5656

57-
execSync(
58-
`npx esbuild ${entryFile} ` +
59-
`--bundle --format=esm --platform=neutral --target=es2020 ` +
60-
`--main-fields=module,main ` +
61-
`${aliasArgs} ` +
62-
`--outfile=${tmpBundle} ` +
63-
`--minify --tree-shaking=true`,
64-
{ stdio: "inherit", cwd: repoRoot },
65-
);
57+
// Use esbuild's JS API rather than spawning the CLI. This avoids passing
58+
// interpolated paths through a shell entirely (resolving the CodeQL
59+
// shell-injection alert) and is portable: the esbuild CLI bin is a native
60+
// binary on some platforms, so it can't be launched via `node`.
61+
await build({
62+
entryPoints: [entryFile],
63+
bundle: true,
64+
format: "esm",
65+
platform: "neutral",
66+
target: "es2020",
67+
mainFields: ["module", "main"],
68+
alias,
69+
outfile: tmpBundle,
70+
minify: true,
71+
treeShaking: true,
72+
});
6673

6774
// ── Step 2: Prepend polyfills ───────────────────────────────────────
6875

scripts/build-modules.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
* 7. Regenerates host-modules.d.ts
1313
*/
1414

15-
import { execSync } from "child_process";
15+
import { execSync, execFileSync } from "child_process";
1616
import { join, dirname } from "path";
1717
import { fileURLToPath } from "url";
18+
import { createRequire } from "module";
1819
import { existsSync, unlinkSync, readdirSync, statSync } from "fs";
1920

2021
const __dirname = dirname(fileURLToPath(import.meta.url));
22+
const require = createRequire(import.meta.url);
2123
const ROOT = join(__dirname, "..");
2224
const BUILTIN_DIR = join(ROOT, "builtin-modules");
2325
const PLUGINS_DIR = join(ROOT, "plugins");
@@ -68,10 +70,20 @@ execSync("npx tsx scripts/generate-ha-modules-dts.ts", {
6870
});
6971

7072
// Step 4: Format with Prettier
71-
execSync(`prettier --write "${BUILTIN_DIR}/*.js"`, {
72-
cwd: ROOT,
73-
stdio: "inherit",
74-
});
73+
// Invoke prettier without a shell (execFileSync) so the interpolated path
74+
// can't be interpreted as a shell command. Prettier expands the glob itself.
75+
execFileSync(
76+
process.execPath,
77+
[
78+
require.resolve("prettier/bin/prettier.cjs"),
79+
"--write",
80+
join(BUILTIN_DIR, "*.js"),
81+
],
82+
{
83+
cwd: ROOT,
84+
stdio: "inherit",
85+
},
86+
);
7587

7688
console.log("\nUpdating module hashes...");
7789

@@ -161,5 +173,4 @@ execSync("npx tsx scripts/generate-host-modules-dts.ts", {
161173
stdio: "inherit",
162174
});
163175

164-
165176
console.log("✓ Build complete");

0 commit comments

Comments
 (0)