Conversation
🟡 Heimdall Review Status
🟡
|
| Code Owner | Status | Calculation | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| ui-systems-eng-team |
🟡
0/1
|
Denominator calculation
|
| const cmd = ['maestro', 'test', flowPath, ...envFlags.map((e) => `--env ${e}`)].join(' '); | ||
|
|
||
| console.log(`\nRunning: ${cmd}\n`); | ||
| execSync(cmd, { stdio: 'inherit', cwd: outputDir }); |
Check warning
Code scanning / CodeQL
Shell command built from environment values Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 days ago
In general, the fix is to avoid constructing a single shell command string and instead call execFileSync (or spawnSync) with the command and its arguments as separate array elements. This way, Node passes arguments directly to the process without an intervening shell, so special characters in paths or environment values cannot change command structure. For this code, the best fix is to change the Maestro invocation so that maestro is the executable and the rest of the tokens (test, flowPath, --env, KEY=VALUE) are elements in an array, then use execFileSync instead of execSync. This preserves functionality but removes shell interpretation.
Concretely in packages/mobile-visreg/src/run.mjs:
- Add
execFileSyncto the import fromchild_process. - Replace the string-building
cmdline with an array of arguments:['test', flowPath, ...envFlags.flatMap(e => ['--env', e])]. - Replace the
execSync(cmd, ...)call withexecFileSync('maestro', args, { stdio: 'inherit', cwd: outputDir });. - Keep the existing console log of the human-readable command string (or reconstruct it from the args) for visibility if desired, but this log should not be executed by the shell.
The earlier execSync that runs node src/generate-flows.mjs ${platform} is technically similar, but it does not involve the tainted flowPath/absolute path and only injects platform (which is limited to 'ios' by default or whatever the caller supplies). Since CodeQL’s specific alerts here are about the Maestro command, we will leave that first execSync unchanged to avoid unrequested behavioral changes.
| @@ -1,4 +1,4 @@ | ||
| import { execSync } from 'child_process'; | ||
| import { execSync, execFileSync } from 'child_process'; | ||
| import { mkdirSync, readdirSync } from 'fs'; | ||
| import { resolve, dirname } from 'path'; | ||
| import { fileURLToPath } from 'url'; | ||
| @@ -50,10 +50,11 @@ | ||
| envFlags.push(`ROUTE_NAME=${route}`); | ||
| } | ||
|
|
||
| const cmd = ['maestro', 'test', flowPath, ...envFlags.map((e) => `--env ${e}`)].join(' '); | ||
| const maestroArgs = ['test', flowPath, ...envFlags.flatMap((e) => ['--env', e])]; | ||
| const cmd = ['maestro', ...maestroArgs].join(' '); | ||
|
|
||
| console.log(`\nRunning: ${cmd}\n`); | ||
| execSync(cmd, { stdio: 'inherit', cwd: outputDir }); | ||
| execFileSync('maestro', maestroArgs, { stdio: 'inherit', cwd: outputDir }); | ||
|
|
||
| const screenshots = readdirSync(outputDir).filter((f) => f.endsWith('.png')); | ||
| console.log(`\nCapture complete: ${screenshots.length} screenshots in ${outputDir}`); |
| const screenshotDir = resolve(dir); | ||
| console.log(`Uploading screenshots from ${screenshotDir} to Percy...`); | ||
|
|
||
| execSync(`npx percy upload ${screenshotDir}`, { stdio: 'inherit' }); |
Check warning
Code scanning / CodeQL
Shell command built from environment values Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 days ago
In general, to fix shell command injection or misinterpretation issues, avoid constructing a single shell command string that includes untrusted values. Instead, invoke the underlying program directly (bypassing the shell) and pass untrusted values as separate arguments in an array, using child_process.execFile / execFileSync or spawn / spawnSync. This prevents the shell from interpreting special characters in the arguments.
For this specific case in packages/mobile-visreg/src/upload.mjs, replace execSync with execFileSync from child_process, and change the invocation from a template string to a command plus arguments array:
- Import
execFileSyncinstead of or in addition toexecSyncat the top of the file. - Replace
execSync(\npx percy upload ${screenshotDir}`, { stdio: 'inherit' });withexecFileSync('npx', ['percy', 'upload', screenshotDir], { stdio: 'inherit' });`.
This keeps all existing behavior (still runs npx percy upload <dir> and inherits stdio) but avoids the shell entirely, so screenshotDir is treated as a literal argument. All changes are confined to packages/mobile-visreg/src/upload.mjs within the shown snippet, and no additional methods are needed beyond the new import.
| @@ -1,4 +1,4 @@ | ||
| import { execSync } from 'child_process'; | ||
| import { execFileSync } from 'child_process'; | ||
| import { resolve } from 'path'; | ||
|
|
||
| function parseArgs() { | ||
| @@ -24,6 +24,6 @@ | ||
| const screenshotDir = resolve(dir); | ||
| console.log(`Uploading screenshots from ${screenshotDir} to Percy...`); | ||
|
|
||
| execSync(`npx percy upload ${screenshotDir}`, { stdio: 'inherit' }); | ||
| execFileSync('npx', ['percy', 'upload', screenshotDir], { stdio: 'inherit' }); | ||
|
|
||
| console.log('\nUpload complete. Visit percy.io to review the build.'); |
94dd9a3 to
30d2737
Compare
1d6940e to
d0b8775
Compare
20d0147 to
1cebb84
Compare
What changed? Why?
Root cause (required for bugfixes)
UI changes
Testing
How has it been tested?
Testing instructions
Illustrations/Icons Checklist
Required if this PR changes files under
packages/illustrations/**orpackages/icons/**Change management
type=routine
risk=low
impact=sev5
automerge=false